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,3804 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
11
|
+
from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
|
|
12
|
+
|
|
13
|
+
from pandaserver.config import panda_config
|
|
14
|
+
from pandaserver.srvcore import CoreUtils
|
|
15
|
+
from pandaserver.taskbuffer import (
|
|
16
|
+
EventServiceUtils,
|
|
17
|
+
GlobalShares,
|
|
18
|
+
JobUtils,
|
|
19
|
+
PrioUtil,
|
|
20
|
+
SiteSpec,
|
|
21
|
+
)
|
|
22
|
+
from pandaserver.taskbuffer.db_proxy_mods.base_module import (
|
|
23
|
+
BaseModule,
|
|
24
|
+
convert_dict_to_bind_vars,
|
|
25
|
+
memoize,
|
|
26
|
+
)
|
|
27
|
+
from pandaserver.taskbuffer.DdmSpec import DdmSpec
|
|
28
|
+
from pandaserver.taskbuffer.JediDatasetSpec import (
|
|
29
|
+
INPUT_TYPES_var_map,
|
|
30
|
+
INPUT_TYPES_var_str,
|
|
31
|
+
)
|
|
32
|
+
from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec
|
|
33
|
+
from pandaserver.taskbuffer.ResourceSpec import ResourceSpec, ResourceSpecMapper
|
|
34
|
+
from pandaserver.taskbuffer.Utils import create_shards
|
|
35
|
+
from pandaserver.taskbuffer.WorkQueueMapper import WorkQueueMapper
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Module class to define methods related to fundamental entities like GlobalShares, Resource Types, CO2, etc
|
|
39
|
+
class EntityModule(BaseModule):
|
|
40
|
+
# constructor
|
|
41
|
+
def __init__(self, log_stream: LogWrapper):
|
|
42
|
+
super().__init__(log_stream)
|
|
43
|
+
# global share variables
|
|
44
|
+
self.tree = None # Pointer to the root of the global shares tree
|
|
45
|
+
self.leave_shares = None # Pointer to the list with leave shares
|
|
46
|
+
self.__t_update_shares = None # Timestamp when the shares were last updated
|
|
47
|
+
self.__hs_distribution = None # HS06s distribution of sites
|
|
48
|
+
self.__t_update_distribution = None # Timestamp when the HS06s distribution was last updated
|
|
49
|
+
|
|
50
|
+
# resource type mapper
|
|
51
|
+
# if you want to use it, you need to call reload_resource_spec_mapper first
|
|
52
|
+
self.resource_spec_mapper = None
|
|
53
|
+
self.__t_update_resource_type_mapper = None
|
|
54
|
+
|
|
55
|
+
# priority boost
|
|
56
|
+
self.job_prio_boost_dict = None
|
|
57
|
+
self.job_prio_boost_dict_update_time = None
|
|
58
|
+
|
|
59
|
+
def __get_hs_leave_distribution(self, leave_shares):
|
|
60
|
+
"""
|
|
61
|
+
Get the current HS06 distribution for running and queued jobs
|
|
62
|
+
"""
|
|
63
|
+
comment = " /* DBProxy.get_hs_leave_distribution */"
|
|
64
|
+
|
|
65
|
+
sql_hs_distribution = """
|
|
66
|
+
SELECT gshare, jobstatus_grouped, SUM(HS)
|
|
67
|
+
FROM
|
|
68
|
+
(SELECT gshare, HS,
|
|
69
|
+
CASE
|
|
70
|
+
WHEN jobstatus IN('activated') THEN 'queued'
|
|
71
|
+
WHEN jobstatus IN('sent', 'running') THEN 'executing'
|
|
72
|
+
ELSE 'ignore'
|
|
73
|
+
END jobstatus_grouped
|
|
74
|
+
FROM ATLAS_PANDA.JOBS_SHARE_STATS JSS) a
|
|
75
|
+
GROUP BY gshare, jobstatus_grouped
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self.cur.execute(sql_hs_distribution + comment)
|
|
79
|
+
hs_distribution_raw = self.cur.fetchall()
|
|
80
|
+
|
|
81
|
+
# get the hs distribution data into a dictionary structure
|
|
82
|
+
hs_distribution_dict = {}
|
|
83
|
+
hs_queued_total = 0
|
|
84
|
+
hs_executing_total = 0
|
|
85
|
+
hs_ignore_total = 0
|
|
86
|
+
for hs_entry in hs_distribution_raw:
|
|
87
|
+
gshare, status_group, hs = hs_entry
|
|
88
|
+
if hs is None:
|
|
89
|
+
continue
|
|
90
|
+
hs = float(hs)
|
|
91
|
+
hs_distribution_dict.setdefault(
|
|
92
|
+
gshare,
|
|
93
|
+
{
|
|
94
|
+
GlobalShares.PLEDGED: 0,
|
|
95
|
+
GlobalShares.QUEUED: 0,
|
|
96
|
+
GlobalShares.EXECUTING: 0,
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
hs_distribution_dict[gshare][status_group] = hs
|
|
100
|
+
# calculate totals
|
|
101
|
+
if status_group == GlobalShares.QUEUED:
|
|
102
|
+
hs_queued_total += hs
|
|
103
|
+
elif status_group == GlobalShares.EXECUTING:
|
|
104
|
+
hs_executing_total += hs
|
|
105
|
+
else:
|
|
106
|
+
hs_ignore_total += hs
|
|
107
|
+
|
|
108
|
+
# Calculate the ideal HS06 distribution based on shares.
|
|
109
|
+
for share_node in leave_shares:
|
|
110
|
+
share_name, share_value = share_node.name, share_node.value
|
|
111
|
+
hs_pledged_share = hs_executing_total * share_value / 100.0
|
|
112
|
+
|
|
113
|
+
hs_distribution_dict.setdefault(
|
|
114
|
+
share_name,
|
|
115
|
+
{
|
|
116
|
+
GlobalShares.PLEDGED: 0,
|
|
117
|
+
GlobalShares.QUEUED: 0,
|
|
118
|
+
GlobalShares.EXECUTING: 0,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
# Pledged HS according to global share definitions
|
|
122
|
+
hs_distribution_dict[share_name]["pledged"] = hs_pledged_share
|
|
123
|
+
return hs_distribution_dict
|
|
124
|
+
|
|
125
|
+
# retrieve global shares
|
|
126
|
+
def get_shares(self, parents=""):
|
|
127
|
+
comment = " /* DBProxy.get_shares */"
|
|
128
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
129
|
+
tmp_log.debug("start")
|
|
130
|
+
|
|
131
|
+
sql = """
|
|
132
|
+
SELECT NAME, VALUE, PARENT, PRODSOURCELABEL, WORKINGGROUP, CAMPAIGN, PROCESSINGTYPE, TRANSPATH, RTYPE,
|
|
133
|
+
VO, QUEUE_ID, THROTTLED
|
|
134
|
+
FROM ATLAS_PANDA.GLOBAL_SHARES
|
|
135
|
+
"""
|
|
136
|
+
var_map = None
|
|
137
|
+
|
|
138
|
+
if parents == "":
|
|
139
|
+
# Get all shares
|
|
140
|
+
pass
|
|
141
|
+
elif parents is None:
|
|
142
|
+
# Get top level shares
|
|
143
|
+
sql += "WHERE parent IS NULL"
|
|
144
|
+
|
|
145
|
+
elif isinstance(parents, str):
|
|
146
|
+
# Get the children of a specific share
|
|
147
|
+
var_map = {":parent": parents}
|
|
148
|
+
sql += "WHERE parent = :parent"
|
|
149
|
+
|
|
150
|
+
elif type(parents) in (list, tuple):
|
|
151
|
+
# Get the children of a list of shares
|
|
152
|
+
var_map = {}
|
|
153
|
+
parent_var_names_str, parent_var_map = get_sql_IN_bind_variables(parents, prefix=":parent")
|
|
154
|
+
sql += f"WHERE parent IN ({parent_var_names_str})"
|
|
155
|
+
var_map.update(parent_var_map)
|
|
156
|
+
|
|
157
|
+
self.cur.execute(sql + comment, var_map)
|
|
158
|
+
resList = self.cur.fetchall()
|
|
159
|
+
|
|
160
|
+
tmp_log.debug("done")
|
|
161
|
+
return resList
|
|
162
|
+
|
|
163
|
+
def reload_shares(self, force=False):
|
|
164
|
+
"""
|
|
165
|
+
Reloads the shares from the DB and recalculates distributions
|
|
166
|
+
"""
|
|
167
|
+
comment = " /* DBProxy.reload_shares */"
|
|
168
|
+
# Don't reload shares every time
|
|
169
|
+
if (self.__t_update_shares is not None and self.__t_update_shares > datetime.datetime.now() - datetime.timedelta(hours=1)) or force:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
173
|
+
|
|
174
|
+
# Root dummy node
|
|
175
|
+
t_before = time.time()
|
|
176
|
+
tree = GlobalShares.Share("root", 100, None, None, None, None, None, None, None, None, None, None)
|
|
177
|
+
t_after = time.time()
|
|
178
|
+
total = t_after - t_before
|
|
179
|
+
tmp_log.debug(f"Root dummy tree took {total}s")
|
|
180
|
+
|
|
181
|
+
# Get top level shares from DB
|
|
182
|
+
t_before = time.time()
|
|
183
|
+
shares_top_level = self.get_shares(parents=None)
|
|
184
|
+
t_after = time.time()
|
|
185
|
+
total = t_after - t_before
|
|
186
|
+
tmp_log.debug(f"Getting shares took {total}s")
|
|
187
|
+
|
|
188
|
+
# Load branches
|
|
189
|
+
t_before = time.time()
|
|
190
|
+
for (
|
|
191
|
+
name,
|
|
192
|
+
value,
|
|
193
|
+
parent,
|
|
194
|
+
prodsourcelabel,
|
|
195
|
+
workinggroup,
|
|
196
|
+
campaign,
|
|
197
|
+
processingtype,
|
|
198
|
+
transpath,
|
|
199
|
+
rtype,
|
|
200
|
+
vo,
|
|
201
|
+
queue_id,
|
|
202
|
+
throttled,
|
|
203
|
+
) in shares_top_level:
|
|
204
|
+
share = GlobalShares.Share(
|
|
205
|
+
name,
|
|
206
|
+
value,
|
|
207
|
+
parent,
|
|
208
|
+
prodsourcelabel,
|
|
209
|
+
workinggroup,
|
|
210
|
+
campaign,
|
|
211
|
+
processingtype,
|
|
212
|
+
transpath,
|
|
213
|
+
rtype,
|
|
214
|
+
vo,
|
|
215
|
+
queue_id,
|
|
216
|
+
throttled,
|
|
217
|
+
)
|
|
218
|
+
tree.children.append(self.__load_branch(share))
|
|
219
|
+
t_after = time.time()
|
|
220
|
+
total = t_after - t_before
|
|
221
|
+
tmp_log.debug(f"Loading the branches took {total}s")
|
|
222
|
+
|
|
223
|
+
# Normalize the values in the database
|
|
224
|
+
t_before = time.time()
|
|
225
|
+
tree.normalize()
|
|
226
|
+
t_after = time.time()
|
|
227
|
+
total = t_after - t_before
|
|
228
|
+
tmp_log.debug(f"Normalizing the values took {total}s")
|
|
229
|
+
|
|
230
|
+
# get the leave shares (the ones not having more children)
|
|
231
|
+
t_before = time.time()
|
|
232
|
+
leave_shares = tree.get_leaves()
|
|
233
|
+
t_after = time.time()
|
|
234
|
+
total = t_after - t_before
|
|
235
|
+
tmp_log.debug(f"Getting the leaves took {total}s")
|
|
236
|
+
|
|
237
|
+
self.leave_shares = leave_shares
|
|
238
|
+
self.__t_update_shares = datetime.datetime.now()
|
|
239
|
+
|
|
240
|
+
# get the distribution of shares
|
|
241
|
+
t_before = time.time()
|
|
242
|
+
# Retrieve the current HS06 distribution of jobs from the database and then aggregate recursively up to the root
|
|
243
|
+
hs_distribution = self.__get_hs_leave_distribution(leave_shares)
|
|
244
|
+
tree.aggregate_hs_distribution(hs_distribution)
|
|
245
|
+
t_after = time.time()
|
|
246
|
+
total = t_after - t_before
|
|
247
|
+
tmp_log.debug(f"Aggregating the hs distribution took {total}s")
|
|
248
|
+
|
|
249
|
+
self.tree = tree
|
|
250
|
+
self.__hs_distribution = hs_distribution
|
|
251
|
+
self.__t_update_distribution = datetime.datetime.now()
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
def __reload_hs_distribution(self):
|
|
255
|
+
"""
|
|
256
|
+
Reloads the HS distribution
|
|
257
|
+
"""
|
|
258
|
+
comment = " /* DBProxy.__reload_hs_distribution */"
|
|
259
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
260
|
+
tmp_log.debug(self.__t_update_distribution)
|
|
261
|
+
tmp_log.debug(self.__hs_distribution)
|
|
262
|
+
# Reload HS06s distribution every 10 seconds
|
|
263
|
+
if self.__t_update_distribution is not None and self.__t_update_distribution > datetime.datetime.now() - datetime.timedelta(seconds=10):
|
|
264
|
+
tmp_log.debug("release")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# Retrieve the current HS06 distribution of jobs from the database and then aggregate recursively up to the root
|
|
268
|
+
tmp_log.debug("get dist")
|
|
269
|
+
t_before = time.time()
|
|
270
|
+
hs_distribution = self.__get_hs_leave_distribution(self.leave_shares)
|
|
271
|
+
tmp_log.debug("aggr dist")
|
|
272
|
+
self.tree.aggregate_hs_distribution(hs_distribution)
|
|
273
|
+
t_after = time.time()
|
|
274
|
+
total = t_after - t_before
|
|
275
|
+
tmp_log.debug(f"Reloading the hs distribution took {total}s")
|
|
276
|
+
|
|
277
|
+
self.__hs_distribution = hs_distribution
|
|
278
|
+
self.__t_update_distribution = datetime.datetime.now()
|
|
279
|
+
|
|
280
|
+
# log the distribution for debugging purposes
|
|
281
|
+
tmp_log.debug(f"Current HS06 distribution is {hs_distribution}")
|
|
282
|
+
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
def get_sorted_leaves(self):
|
|
286
|
+
"""
|
|
287
|
+
Re-loads the shares, then returns the leaves sorted by under usage
|
|
288
|
+
"""
|
|
289
|
+
self.reload_shares()
|
|
290
|
+
self.__reload_hs_distribution()
|
|
291
|
+
return self.tree.sort_branch_by_current_hs_distribution(self.__hs_distribution)
|
|
292
|
+
|
|
293
|
+
def get_tree_of_gshare_names(self):
|
|
294
|
+
"""
|
|
295
|
+
get nested dict of gshare names implying the tree structure
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
def get_nested_gshare(share):
|
|
299
|
+
val = None
|
|
300
|
+
if not share.children:
|
|
301
|
+
# leaf
|
|
302
|
+
pass
|
|
303
|
+
else:
|
|
304
|
+
# branch
|
|
305
|
+
val = {}
|
|
306
|
+
for child in share.children:
|
|
307
|
+
val[child.name] = get_nested_gshare(child)
|
|
308
|
+
return val
|
|
309
|
+
|
|
310
|
+
ret_dict = get_nested_gshare(self.tree)
|
|
311
|
+
return ret_dict
|
|
312
|
+
|
|
313
|
+
def __load_branch(self, share):
|
|
314
|
+
"""
|
|
315
|
+
Recursively load a branch
|
|
316
|
+
"""
|
|
317
|
+
node = GlobalShares.Share(
|
|
318
|
+
share.name,
|
|
319
|
+
share.value,
|
|
320
|
+
share.parent,
|
|
321
|
+
share.prodsourcelabel,
|
|
322
|
+
share.workinggroup,
|
|
323
|
+
share.campaign,
|
|
324
|
+
share.processingtype,
|
|
325
|
+
share.transpath,
|
|
326
|
+
share.rtype,
|
|
327
|
+
share.vo,
|
|
328
|
+
share.queue_id,
|
|
329
|
+
share.throttled,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
children = self.get_shares(parents=share.name)
|
|
333
|
+
if not children:
|
|
334
|
+
return node
|
|
335
|
+
|
|
336
|
+
for (
|
|
337
|
+
name,
|
|
338
|
+
value,
|
|
339
|
+
parent,
|
|
340
|
+
prodsourcelabel,
|
|
341
|
+
workinggroup,
|
|
342
|
+
campaign,
|
|
343
|
+
processingtype,
|
|
344
|
+
transpath,
|
|
345
|
+
rtype,
|
|
346
|
+
vo,
|
|
347
|
+
queue_id,
|
|
348
|
+
throttled,
|
|
349
|
+
) in children:
|
|
350
|
+
child = GlobalShares.Share(
|
|
351
|
+
name,
|
|
352
|
+
value,
|
|
353
|
+
parent,
|
|
354
|
+
prodsourcelabel,
|
|
355
|
+
workinggroup,
|
|
356
|
+
campaign,
|
|
357
|
+
processingtype,
|
|
358
|
+
transpath,
|
|
359
|
+
rtype,
|
|
360
|
+
vo,
|
|
361
|
+
queue_id,
|
|
362
|
+
throttled,
|
|
363
|
+
)
|
|
364
|
+
node.children.append(self.__load_branch(child))
|
|
365
|
+
|
|
366
|
+
return node
|
|
367
|
+
|
|
368
|
+
def getGShareStatus(self):
|
|
369
|
+
"""
|
|
370
|
+
Generates a list with sorted leave branches
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
comment = " /* DBProxy.getGShareStatus */"
|
|
374
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
375
|
+
tmp_log.debug("start")
|
|
376
|
+
|
|
377
|
+
self.reload_shares()
|
|
378
|
+
self.__reload_hs_distribution()
|
|
379
|
+
sorted_shares = self.tree.sort_branch_by_current_hs_distribution(self.__hs_distribution)
|
|
380
|
+
|
|
381
|
+
sorted_shares_export = []
|
|
382
|
+
for share in sorted_shares:
|
|
383
|
+
sorted_shares_export.append(
|
|
384
|
+
{
|
|
385
|
+
"name": share.name,
|
|
386
|
+
"running": self.__hs_distribution[share.name]["executing"],
|
|
387
|
+
"target": self.__hs_distribution[share.name]["pledged"],
|
|
388
|
+
"queuing": self.__hs_distribution[share.name]["queued"],
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
return sorted_shares_export
|
|
392
|
+
|
|
393
|
+
def is_valid_share(self, share_name):
|
|
394
|
+
"""
|
|
395
|
+
Checks whether the share is a valid leave share
|
|
396
|
+
"""
|
|
397
|
+
self.reload_shares()
|
|
398
|
+
for share in self.leave_shares:
|
|
399
|
+
if share_name == share.name:
|
|
400
|
+
# Share found
|
|
401
|
+
return True
|
|
402
|
+
|
|
403
|
+
# Share not found
|
|
404
|
+
return False
|
|
405
|
+
|
|
406
|
+
def reassignShare(self, jedi_task_ids, gshare, reassign_running):
|
|
407
|
+
"""
|
|
408
|
+
Will reassign all tasks and their jobs that have not yet completed to specified share
|
|
409
|
+
@param jedi_task_ids: task ids
|
|
410
|
+
@param gshare: dest share
|
|
411
|
+
@param reassign_running: whether to reassign running jobs
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
comment = " /* DBProxy.reassignShare */"
|
|
415
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
416
|
+
tmp_log.debug(f"Start reassigning {jedi_task_ids} to gshare={gshare} and reassign_running={reassign_running}")
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
if not self.is_valid_share(gshare):
|
|
420
|
+
error_msg = f"Share {gshare} is not a leave share "
|
|
421
|
+
tmp_log.debug(error_msg)
|
|
422
|
+
ret_val = 1, error_msg
|
|
423
|
+
tmp_log.debug(error_msg)
|
|
424
|
+
return ret_val
|
|
425
|
+
|
|
426
|
+
# begin transaction
|
|
427
|
+
self.conn.begin()
|
|
428
|
+
|
|
429
|
+
# update in shards of 100 task ids
|
|
430
|
+
for shard in create_shards(jedi_task_ids, 100):
|
|
431
|
+
# Prepare the bindings
|
|
432
|
+
var_map = {}
|
|
433
|
+
shard_taskid_set = set()
|
|
434
|
+
i = 0
|
|
435
|
+
for _task_id in shard:
|
|
436
|
+
jedi_task_id = int(_task_id)
|
|
437
|
+
var_map[f":jtid{i}"] = jedi_task_id
|
|
438
|
+
i += 1
|
|
439
|
+
shard_taskid_set.add(jedi_task_id)
|
|
440
|
+
jtid_bindings = ",".join(f":jtid{i}" for i in range(len(shard_taskid_set)))
|
|
441
|
+
|
|
442
|
+
# select only tasks without lock
|
|
443
|
+
sql_tasks_not_locked = f"""
|
|
444
|
+
SELECT jediTaskID FROM ATLAS_PANDA.JEDI_Tasks
|
|
445
|
+
WHERE jediTaskID IN ({jtid_bindings}) AND lockedBy IS NULL
|
|
446
|
+
"""
|
|
447
|
+
|
|
448
|
+
self.cur.execute(sql_tasks_not_locked + comment, var_map)
|
|
449
|
+
res = self.cur.fetchall()
|
|
450
|
+
|
|
451
|
+
# Update the bindings and prepare var map
|
|
452
|
+
var_map = {":gshare": gshare}
|
|
453
|
+
good_taskid_set = set()
|
|
454
|
+
i = 0
|
|
455
|
+
for (_task_id,) in res:
|
|
456
|
+
jedi_task_id = int(_task_id)
|
|
457
|
+
var_map[f":jtid{i}"] = jedi_task_id
|
|
458
|
+
i += 1
|
|
459
|
+
good_taskid_set.add(jedi_task_id)
|
|
460
|
+
jtid_bindings = ",".join(f":jtid{i}" for i in range(len(good_taskid_set)))
|
|
461
|
+
locked_taskid_set = shard_taskid_set - good_taskid_set
|
|
462
|
+
if locked_taskid_set:
|
|
463
|
+
tmp_log.debug(f"skip locked tasks: {','.join([str(i) for i in locked_taskid_set])}")
|
|
464
|
+
|
|
465
|
+
# Skip if there are no tasks to update
|
|
466
|
+
if not jtid_bindings:
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
# update the task
|
|
470
|
+
sql_task = f"""
|
|
471
|
+
UPDATE ATLAS_PANDA.jedi_tasks set gshare=:gshare
|
|
472
|
+
WHERE jeditaskid IN ({jtid_bindings})
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
self.cur.execute(sql_task + comment, var_map)
|
|
476
|
+
tmp_log.debug(f"""set tasks {",".join([str(i) for i in good_taskid_set])} to gshare={gshare}""")
|
|
477
|
+
|
|
478
|
+
var_map[":pending"] = "pending"
|
|
479
|
+
var_map[":defined"] = "defined"
|
|
480
|
+
var_map[":assigned"] = "assigned"
|
|
481
|
+
var_map[":waiting"] = "waiting"
|
|
482
|
+
var_map[":activated"] = "activated"
|
|
483
|
+
jobstatus = ":pending, :defined, :assigned, :waiting, :activated"
|
|
484
|
+
|
|
485
|
+
# add running status in case these jobs also need to be reassigned
|
|
486
|
+
if reassign_running:
|
|
487
|
+
jobstatus = f"{jobstatus}, :running, :starting"
|
|
488
|
+
var_map[":running"] = "running"
|
|
489
|
+
var_map[":starting"] = "starting"
|
|
490
|
+
|
|
491
|
+
# update the jobs
|
|
492
|
+
sql_jobs = """
|
|
493
|
+
UPDATE ATLAS_PANDA.{0} set gshare=:gshare
|
|
494
|
+
WHERE jeditaskid IN ({1})
|
|
495
|
+
AND jobstatus IN ({2})
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
for table in ["jobsactive4", "jobsdefined4"]:
|
|
499
|
+
self.cur.execute(
|
|
500
|
+
sql_jobs.format(table, jtid_bindings, jobstatus) + comment,
|
|
501
|
+
var_map,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# commit
|
|
505
|
+
if not self._commit():
|
|
506
|
+
raise RuntimeError("Commit error")
|
|
507
|
+
|
|
508
|
+
tmp_log.debug("done")
|
|
509
|
+
return 0, None
|
|
510
|
+
|
|
511
|
+
except Exception:
|
|
512
|
+
# roll back
|
|
513
|
+
self._rollback()
|
|
514
|
+
# dump error
|
|
515
|
+
self.dump_error_message(tmp_log)
|
|
516
|
+
return -1, None
|
|
517
|
+
|
|
518
|
+
def reload_resource_spec_mapper(self):
|
|
519
|
+
# update once per hour only
|
|
520
|
+
if self.__t_update_resource_type_mapper and self.__t_update_resource_type_mapper > datetime.datetime.now() - datetime.timedelta(hours=1):
|
|
521
|
+
return
|
|
522
|
+
|
|
523
|
+
# get the resource types from the DB and make the ResourceSpecMapper object
|
|
524
|
+
resource_types = self.load_resource_types(use_commit=False)
|
|
525
|
+
if resource_types:
|
|
526
|
+
self.resource_spec_mapper = ResourceSpecMapper(resource_types)
|
|
527
|
+
self.__t_update_resource_type_mapper = datetime.datetime.now()
|
|
528
|
+
return
|
|
529
|
+
|
|
530
|
+
def load_resource_types(self, formatting="spec", use_commit=True):
|
|
531
|
+
"""
|
|
532
|
+
Load the resource type table to memory
|
|
533
|
+
"""
|
|
534
|
+
comment = " /* JediDBProxy.load_resource_types */"
|
|
535
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
536
|
+
tmp_log.debug("start")
|
|
537
|
+
try:
|
|
538
|
+
sql = f"SELECT {ResourceSpec.column_names()} FROM {panda_config.schemaJEDI}.resource_types "
|
|
539
|
+
self.cur.execute(sql + comment)
|
|
540
|
+
resource_list = self.cur.fetchall()
|
|
541
|
+
resource_spec_list = []
|
|
542
|
+
for row in resource_list:
|
|
543
|
+
resource_name, mincore, maxcore, minrampercore, maxrampercore = row
|
|
544
|
+
if formatting == "dict":
|
|
545
|
+
resource_dict = {
|
|
546
|
+
"resource_name": resource_name,
|
|
547
|
+
"mincore": mincore,
|
|
548
|
+
"maxcore": maxcore,
|
|
549
|
+
"minrampercore": minrampercore,
|
|
550
|
+
"maxrampercore": maxrampercore,
|
|
551
|
+
}
|
|
552
|
+
resource_spec_list.append(resource_dict)
|
|
553
|
+
else:
|
|
554
|
+
resource_spec_list.append(
|
|
555
|
+
ResourceSpec(
|
|
556
|
+
resource_name,
|
|
557
|
+
mincore,
|
|
558
|
+
maxcore,
|
|
559
|
+
minrampercore,
|
|
560
|
+
maxrampercore,
|
|
561
|
+
)
|
|
562
|
+
)
|
|
563
|
+
# commit for postgres to avoid idle transactions
|
|
564
|
+
if use_commit:
|
|
565
|
+
if not self._commit():
|
|
566
|
+
raise RuntimeError("Commit error")
|
|
567
|
+
tmp_log.debug("done")
|
|
568
|
+
return resource_spec_list
|
|
569
|
+
except Exception:
|
|
570
|
+
if use_commit:
|
|
571
|
+
self._rollback()
|
|
572
|
+
self.dump_error_message(tmp_log)
|
|
573
|
+
return []
|
|
574
|
+
|
|
575
|
+
def get_resource_type_task(self, task_spec):
|
|
576
|
+
"""
|
|
577
|
+
Identify the resource type of the task based on the resource type map.
|
|
578
|
+
Return the name of the resource type
|
|
579
|
+
"""
|
|
580
|
+
comment = " /* JediDBProxy.get_resource_type_task */"
|
|
581
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
582
|
+
tmp_log.debug("start")
|
|
583
|
+
|
|
584
|
+
resource_map = self.load_resource_types()
|
|
585
|
+
|
|
586
|
+
for resource_spec in resource_map:
|
|
587
|
+
if resource_spec.match_task(task_spec):
|
|
588
|
+
tmp_log.debug(f"done. resource_type is {resource_spec.resource_name}")
|
|
589
|
+
return resource_spec.resource_name
|
|
590
|
+
|
|
591
|
+
tmp_log.debug("done. resource_type is Undefined")
|
|
592
|
+
return "Undefined"
|
|
593
|
+
|
|
594
|
+
def reset_resource_type_task(self, jedi_task_id, use_commit=True):
|
|
595
|
+
"""
|
|
596
|
+
Retrieve the relevant task parameters and reset the resource type
|
|
597
|
+
"""
|
|
598
|
+
comment = " /* JediDBProxy.reset_resource_type */"
|
|
599
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
600
|
+
tmp_log.debug("start")
|
|
601
|
+
|
|
602
|
+
# 1. Get the task parameters
|
|
603
|
+
var_map = {":jedi_task_id": jedi_task_id}
|
|
604
|
+
sql = f"SELECT corecount, ramcount, baseramcount, ramunit FROM {panda_config.schemaJEDI}.jedi_tasks WHERE jeditaskid = :jedi_task_id "
|
|
605
|
+
self.cur.execute(sql + comment, var_map)
|
|
606
|
+
corecount, ramcount, baseramcount, ramunit = self.cur.fetchone()
|
|
607
|
+
tmp_log.debug(
|
|
608
|
+
"retrieved following values for jediTaskid {0}: corecount {1}, ramcount {2}, baseramcount {3}, ramunit {4}".format(
|
|
609
|
+
jedi_task_id, corecount, ramcount, baseramcount, ramunit
|
|
610
|
+
)
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# 2. Load the resource types and figure out the matching one
|
|
614
|
+
resource_map = self.load_resource_types(use_commit=False)
|
|
615
|
+
resource_name = "Undefined"
|
|
616
|
+
for resource_spec in resource_map:
|
|
617
|
+
if resource_spec.match_task_basic(corecount, ramcount, baseramcount, ramunit):
|
|
618
|
+
resource_name = resource_spec.resource_name
|
|
619
|
+
break
|
|
620
|
+
|
|
621
|
+
tmp_log.debug(f"decided resource_type {resource_name} jediTaskid {jedi_task_id}")
|
|
622
|
+
|
|
623
|
+
# 3. Update the task
|
|
624
|
+
try:
|
|
625
|
+
var_map = {":jedi_task_id": jedi_task_id, ":resource_type": resource_name}
|
|
626
|
+
sql = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET resource_type = :resource_type WHERE jeditaskid = :jedi_task_id "
|
|
627
|
+
tmp_log.debug("conn begin...")
|
|
628
|
+
if use_commit:
|
|
629
|
+
self.conn.begin()
|
|
630
|
+
tmp_log.debug("execute...")
|
|
631
|
+
self.cur.execute(sql + comment, var_map)
|
|
632
|
+
tmp_log.debug("commit...")
|
|
633
|
+
if use_commit:
|
|
634
|
+
if not self._commit():
|
|
635
|
+
raise RuntimeError("Commit error")
|
|
636
|
+
tmp_log.debug("committed...")
|
|
637
|
+
except Exception:
|
|
638
|
+
# roll back
|
|
639
|
+
if use_commit:
|
|
640
|
+
tmp_log.debug("rolling back...")
|
|
641
|
+
self._rollback()
|
|
642
|
+
self.dump_error_message(tmp_log)
|
|
643
|
+
tmp_log.error(f"{comment}: {sql} {var_map}")
|
|
644
|
+
return False
|
|
645
|
+
|
|
646
|
+
tmp_log.debug("done")
|
|
647
|
+
return True
|
|
648
|
+
|
|
649
|
+
def get_resource_type_job(self, job_spec):
|
|
650
|
+
"""
|
|
651
|
+
Identify the resource type of the job based on the resource type map.
|
|
652
|
+
Return the name of the resource type
|
|
653
|
+
"""
|
|
654
|
+
comment = " /* JediDBProxy.get_resource_type_job */"
|
|
655
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
656
|
+
tmp_log.debug("start")
|
|
657
|
+
|
|
658
|
+
resource_map = self.load_resource_types()
|
|
659
|
+
tmp_log.debug(
|
|
660
|
+
"going to call match_job for pandaid {0} with minRamCount {1} (type{2}) and coreCount {3} (type{4})".format(
|
|
661
|
+
job_spec.PandaID,
|
|
662
|
+
job_spec.minRamCount,
|
|
663
|
+
type(job_spec.minRamCount),
|
|
664
|
+
job_spec.coreCount,
|
|
665
|
+
type(job_spec.coreCount),
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
resource_type = JobUtils.get_resource_type_job(resource_map, job_spec)
|
|
669
|
+
tmp_log.debug(f"done. resource_type is {resource_type}")
|
|
670
|
+
return resource_type
|
|
671
|
+
|
|
672
|
+
# get the resource type of a site
|
|
673
|
+
def get_rtype_site(self, site):
|
|
674
|
+
comment = " /* DBProxy.get_rtype_site */"
|
|
675
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
676
|
+
tmp_log.debug("start")
|
|
677
|
+
try:
|
|
678
|
+
var_map = {":site": site}
|
|
679
|
+
sql = "SELECT /* use_json_type */ scj.data.resource_type FROM ATLAS_PANDA.schedconfig_json scj WHERE panda_queue=:site "
|
|
680
|
+
|
|
681
|
+
# start transaction
|
|
682
|
+
self.conn.begin()
|
|
683
|
+
self.cur.arraysize = 1
|
|
684
|
+
self.cur.execute(sql + comment, var_map)
|
|
685
|
+
rtype = self.cur.fetchone()[0]
|
|
686
|
+
# commit
|
|
687
|
+
if not self._commit():
|
|
688
|
+
raise RuntimeError("Commit error")
|
|
689
|
+
tmp_log.debug(f"site {site} has rtype: {rtype} ")
|
|
690
|
+
return rtype
|
|
691
|
+
except Exception:
|
|
692
|
+
# roll back
|
|
693
|
+
self._rollback()
|
|
694
|
+
# error
|
|
695
|
+
self.dump_error_message(tmp_log)
|
|
696
|
+
return None
|
|
697
|
+
|
|
698
|
+
def compare_share_task(self, share, task):
|
|
699
|
+
"""
|
|
700
|
+
Logic to compare the relevant fields of share and task.
|
|
701
|
+
Return: False if some condition does NOT match. True if all conditions match.
|
|
702
|
+
"""
|
|
703
|
+
if share.prodsourcelabel is not None and task.prodSourceLabel is not None and re.match(share.prodsourcelabel, task.prodSourceLabel) is None:
|
|
704
|
+
return False
|
|
705
|
+
|
|
706
|
+
working_group = task.workingGroup
|
|
707
|
+
if working_group is None:
|
|
708
|
+
working_group = " "
|
|
709
|
+
|
|
710
|
+
if share.workinggroup is not None and working_group is not None and re.match(share.workinggroup, working_group) is None:
|
|
711
|
+
return False
|
|
712
|
+
|
|
713
|
+
if share.campaign is not None and task.campaign and re.match(share.campaign, task.campaign) is None:
|
|
714
|
+
return False
|
|
715
|
+
|
|
716
|
+
if share.processingtype is not None and task.processingType is not None and re.match(share.processingtype, task.processingType) is None:
|
|
717
|
+
return False
|
|
718
|
+
|
|
719
|
+
if share.transpath is not None and task.transPath is not None and re.match(share.transpath, task.transPath) is None:
|
|
720
|
+
return False
|
|
721
|
+
|
|
722
|
+
if share.rtype is not None and task.site is not None:
|
|
723
|
+
try:
|
|
724
|
+
site = task.site.split(",")[0] # if task assigned to more than one site, take the first one
|
|
725
|
+
rtype_site = self.get_rtype_site(site)
|
|
726
|
+
if rtype_site and re.match(share.rtype, rtype_site) is None:
|
|
727
|
+
return False
|
|
728
|
+
except Exception:
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
return True
|
|
732
|
+
|
|
733
|
+
def compare_share_job(self, share, job):
|
|
734
|
+
"""
|
|
735
|
+
Logic to compare the relevant fields of share and job. It's basically the same as compare_share_task, but
|
|
736
|
+
does not check for the campaign field, which is not part of the job
|
|
737
|
+
"""
|
|
738
|
+
|
|
739
|
+
if share.prodsourcelabel is not None and re.match(share.prodsourcelabel, job.prodSourceLabel) is None:
|
|
740
|
+
return False
|
|
741
|
+
|
|
742
|
+
if share.workinggroup is not None and re.match(share.workinggroup, job.workingGroup) is None:
|
|
743
|
+
return False
|
|
744
|
+
|
|
745
|
+
if share.processingtype is not None and re.match(share.processingtype, job.processingType) is None:
|
|
746
|
+
return False
|
|
747
|
+
|
|
748
|
+
if share.rtype is not None and job.computingSite is not None:
|
|
749
|
+
try:
|
|
750
|
+
site = job.computingSite.split(",")[0]
|
|
751
|
+
rtype_site = self.get_rtype_site(site)
|
|
752
|
+
if re.match(share.rtype, rtype_site) is None:
|
|
753
|
+
return False
|
|
754
|
+
except Exception:
|
|
755
|
+
return False
|
|
756
|
+
|
|
757
|
+
return True
|
|
758
|
+
|
|
759
|
+
def get_share_for_task(self, task):
|
|
760
|
+
"""
|
|
761
|
+
Return the share based on a task specification
|
|
762
|
+
"""
|
|
763
|
+
self.reload_shares()
|
|
764
|
+
selected_share_name = "Undefined"
|
|
765
|
+
|
|
766
|
+
for share in self.leave_shares:
|
|
767
|
+
if self.compare_share_task(share, task):
|
|
768
|
+
selected_share_name = share.name
|
|
769
|
+
break
|
|
770
|
+
|
|
771
|
+
if selected_share_name == "Undefined":
|
|
772
|
+
self._log_stream.warning(
|
|
773
|
+
"No share matching jediTaskId={0} (prodSourceLabel={1} workingGroup={2} campaign={3} transpath={4} site={5})".format(
|
|
774
|
+
task.jediTaskID,
|
|
775
|
+
task.prodSourceLabel,
|
|
776
|
+
task.workingGroup,
|
|
777
|
+
task.campaign,
|
|
778
|
+
task.transPath,
|
|
779
|
+
task.site,
|
|
780
|
+
)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
return selected_share_name
|
|
784
|
+
|
|
785
|
+
def get_share_for_job(self, job):
|
|
786
|
+
"""
|
|
787
|
+
Return the share based on a job specification
|
|
788
|
+
"""
|
|
789
|
+
# special case: esmerge jobs go to Express share
|
|
790
|
+
if job.eventService == EventServiceUtils.esMergeJobFlagNumber:
|
|
791
|
+
return "Express"
|
|
792
|
+
|
|
793
|
+
self.reload_shares()
|
|
794
|
+
selected_share_name = "Undefined"
|
|
795
|
+
|
|
796
|
+
for share in self.leave_shares:
|
|
797
|
+
if self.compare_share_job(share, job):
|
|
798
|
+
selected_share_name = share.name
|
|
799
|
+
break
|
|
800
|
+
|
|
801
|
+
if selected_share_name == "Undefined":
|
|
802
|
+
self._log_stream.warning(f"No share matching PandaID={job.PandaID} (prodSourceLabel={job.prodSourceLabel} workingGroup={job.workingGroup})")
|
|
803
|
+
|
|
804
|
+
return selected_share_name
|
|
805
|
+
|
|
806
|
+
# get dispatch sorting criteria
|
|
807
|
+
def getSortingCriteria(self, site_name, max_jobs):
|
|
808
|
+
comment = " /* DBProxy.getSortingCriteria */"
|
|
809
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
810
|
+
# throw the dice to decide the algorithm
|
|
811
|
+
random_number = random.randrange(100)
|
|
812
|
+
|
|
813
|
+
sloppy_ratio = self.getConfigValue("jobdispatch", "SLOPPY_DISPATCH_RATIO")
|
|
814
|
+
if not sloppy_ratio:
|
|
815
|
+
sloppy_ratio = 10
|
|
816
|
+
|
|
817
|
+
tmp_log.debug(f"random_number: {random_number} sloppy_ratio: {sloppy_ratio}")
|
|
818
|
+
|
|
819
|
+
if random_number <= sloppy_ratio:
|
|
820
|
+
# generate the age sorting
|
|
821
|
+
tmp_log.debug(f"sorting by age")
|
|
822
|
+
return self.getCriteriaByAge(site_name, max_jobs)
|
|
823
|
+
else:
|
|
824
|
+
# generate the global share sorting
|
|
825
|
+
tmp_log.debug(f"sorting by gshare")
|
|
826
|
+
return self.getCriteriaForGlobalShares(site_name, max_jobs)
|
|
827
|
+
|
|
828
|
+
# get selection criteria for share of production activities
|
|
829
|
+
def getCriteriaForGlobalShares(self, site_name, max_jobs):
|
|
830
|
+
comment = " /* DBProxy.getCriteriaForGlobalShare */"
|
|
831
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
832
|
+
# return for no criteria
|
|
833
|
+
var_map = {}
|
|
834
|
+
ret_empty = "", {}
|
|
835
|
+
|
|
836
|
+
try:
|
|
837
|
+
# Get the share leaves sorted by order of under-pledging
|
|
838
|
+
tmp_log.debug(f"Going to call get sorted leaves")
|
|
839
|
+
t_before = time.time()
|
|
840
|
+
sorted_leaves = self.get_sorted_leaves()
|
|
841
|
+
t_after = time.time()
|
|
842
|
+
total = t_after - t_before
|
|
843
|
+
tmp_log.debug(f"Sorting leaves took {total}s")
|
|
844
|
+
|
|
845
|
+
i = 0
|
|
846
|
+
tmp_list = []
|
|
847
|
+
for leave in sorted_leaves:
|
|
848
|
+
var_map[f":leave{i}"] = leave.name
|
|
849
|
+
if leave.name == "Test":
|
|
850
|
+
# Test share will bypass others for the moment
|
|
851
|
+
tmp_list.append(f"WHEN gshare=:leave{i} THEN 0")
|
|
852
|
+
else:
|
|
853
|
+
tmp_list.append("WHEN gshare=:leave{0} THEN {0}".format(i))
|
|
854
|
+
i += 1
|
|
855
|
+
|
|
856
|
+
# Only get max_jobs, to avoid getting all activated jobs from the table
|
|
857
|
+
var_map[":njobs"] = max_jobs
|
|
858
|
+
|
|
859
|
+
# We want to sort by global share, highest priority and lowest pandaid
|
|
860
|
+
leave_bindings = " ".join(tmp_list)
|
|
861
|
+
ret_sql = f"""
|
|
862
|
+
ORDER BY (CASE {leave_bindings} ELSE {len(sorted_leaves)} END), currentpriority desc, pandaid asc)
|
|
863
|
+
WHERE ROWNUM <= :njobs
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
tmp_log.debug(f"ret_sql: {ret_sql}. var_map: {var_map}")
|
|
867
|
+
return ret_sql, var_map
|
|
868
|
+
|
|
869
|
+
except Exception:
|
|
870
|
+
# roll back
|
|
871
|
+
self._rollback()
|
|
872
|
+
self.dump_error_message(tmp_log)
|
|
873
|
+
return ret_empty
|
|
874
|
+
|
|
875
|
+
# get selection criteria for share of production activities
|
|
876
|
+
def getCriteriaByAge(self, site_name, max_jobs):
|
|
877
|
+
comment = " /* DBProxy.getCriteriaByAge */"
|
|
878
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
879
|
+
# return for no criteria
|
|
880
|
+
ret_sql = ""
|
|
881
|
+
var_map = {}
|
|
882
|
+
ret_empty = "", {}
|
|
883
|
+
|
|
884
|
+
try:
|
|
885
|
+
# Only get max_jobs, to avoid getting all activated jobs from the table
|
|
886
|
+
var_map[":njobs"] = max_jobs
|
|
887
|
+
|
|
888
|
+
# We want to ignore global share and just take the oldest pandaid
|
|
889
|
+
ret_sql = """
|
|
890
|
+
ORDER BY pandaid asc)
|
|
891
|
+
WHERE ROWNUM <= :njobs
|
|
892
|
+
"""
|
|
893
|
+
|
|
894
|
+
tmp_log.debug(f"ret_sql: {ret_sql}. var_map: {var_map}")
|
|
895
|
+
return ret_sql, var_map
|
|
896
|
+
|
|
897
|
+
except Exception:
|
|
898
|
+
# roll back
|
|
899
|
+
self._rollback()
|
|
900
|
+
self.dump_error_message(tmp_log)
|
|
901
|
+
return ret_empty
|
|
902
|
+
|
|
903
|
+
# set HS06sec
|
|
904
|
+
def setHS06sec(self, pandaID, inActive=False):
|
|
905
|
+
comment = " /* DBProxy.setHS06sec */"
|
|
906
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={pandaID}")
|
|
907
|
+
tmp_log.debug("start")
|
|
908
|
+
hs06sec = None
|
|
909
|
+
|
|
910
|
+
# sql to get job attributes
|
|
911
|
+
sqlJ = "SELECT jediTaskID,startTime,endTime,actualCoreCount,coreCount,jobMetrics,computingSite "
|
|
912
|
+
if inActive:
|
|
913
|
+
sqlJ += "FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
|
|
914
|
+
else:
|
|
915
|
+
sqlJ += "FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
|
|
916
|
+
|
|
917
|
+
# sql to update HS06sec
|
|
918
|
+
if inActive:
|
|
919
|
+
sqlU = "UPDATE ATLAS_PANDA.jobsActive4 "
|
|
920
|
+
else:
|
|
921
|
+
sqlU = "UPDATE ATLAS_PANDA.jobsArchived4 "
|
|
922
|
+
sqlU += "SET hs06sec=:hs06sec WHERE PandaID=:PandaID "
|
|
923
|
+
|
|
924
|
+
# get job attributes
|
|
925
|
+
varMap = {}
|
|
926
|
+
varMap[":PandaID"] = pandaID
|
|
927
|
+
self.cur.execute(sqlJ + comment, varMap)
|
|
928
|
+
resJ = self.cur.fetchone()
|
|
929
|
+
if resJ is None:
|
|
930
|
+
tmp_log.debug("skip since job not found")
|
|
931
|
+
else:
|
|
932
|
+
(
|
|
933
|
+
jediTaskID,
|
|
934
|
+
startTime,
|
|
935
|
+
endTime,
|
|
936
|
+
actualCoreCount,
|
|
937
|
+
defCoreCount,
|
|
938
|
+
jobMetrics,
|
|
939
|
+
computingSite,
|
|
940
|
+
) = resJ
|
|
941
|
+
# get corePower
|
|
942
|
+
corePower, tmpMsg = self.get_core_power(computingSite)
|
|
943
|
+
if corePower is None:
|
|
944
|
+
tmp_log.debug(f"skip since corePower is undefined for site={computingSite}")
|
|
945
|
+
else:
|
|
946
|
+
# get core count
|
|
947
|
+
coreCount = JobUtils.getCoreCount(actualCoreCount, defCoreCount, jobMetrics)
|
|
948
|
+
# get HS06sec
|
|
949
|
+
hs06sec = JobUtils.getHS06sec(startTime, endTime, corePower, coreCount)
|
|
950
|
+
if hs06sec is None:
|
|
951
|
+
tmp_log.debug("skip since HS06sec is None")
|
|
952
|
+
else:
|
|
953
|
+
# cap
|
|
954
|
+
hs06sec = int(hs06sec)
|
|
955
|
+
maxHS06sec = 999999999
|
|
956
|
+
if hs06sec > maxHS06sec:
|
|
957
|
+
hs06sec = maxHS06sec
|
|
958
|
+
# update HS06sec
|
|
959
|
+
varMap = {}
|
|
960
|
+
varMap[":PandaID"] = pandaID
|
|
961
|
+
varMap[":hs06sec"] = hs06sec
|
|
962
|
+
self.cur.execute(sqlU + comment, varMap)
|
|
963
|
+
tmp_log.debug(f"set HS06sec={hs06sec}")
|
|
964
|
+
# return
|
|
965
|
+
return hs06sec
|
|
966
|
+
|
|
967
|
+
def convert_computingsite_to_region(self, computing_site):
|
|
968
|
+
comment = " /* DBProxy.convert_computingsite_to_region */"
|
|
969
|
+
|
|
970
|
+
var_map = {":panda_queue": computing_site}
|
|
971
|
+
sql = "SELECT /* use_json_type */ scj.data.region FROM ATLAS_PANDA.schedconfig_json scj WHERE scj.panda_queue=:panda_queue"
|
|
972
|
+
self.cur.arraysize = 100
|
|
973
|
+
self.cur.execute(sql + comment, var_map)
|
|
974
|
+
res_region = self.cur.fetchone()
|
|
975
|
+
region = "GRID" # when region is not defined, take average values
|
|
976
|
+
if res_region:
|
|
977
|
+
region = res_region[0]
|
|
978
|
+
|
|
979
|
+
return region
|
|
980
|
+
|
|
981
|
+
def get_co2_emissions_site(self, computing_site):
|
|
982
|
+
comment = " /* DBProxy.get_co2_emissions_site */"
|
|
983
|
+
region = self.convert_computingsite_to_region(computing_site)
|
|
984
|
+
if not region:
|
|
985
|
+
return None
|
|
986
|
+
|
|
987
|
+
var_map = {":region": region}
|
|
988
|
+
sql = "SELECT timestamp, region, value FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS WHERE region=:region"
|
|
989
|
+
self.cur.execute(sql + comment, var_map)
|
|
990
|
+
results = self.cur.fetchall()
|
|
991
|
+
return results
|
|
992
|
+
|
|
993
|
+
def get_co2_emissions_grid(self):
|
|
994
|
+
comment = " /* DBProxy.get_co2_emissions_grid */"
|
|
995
|
+
|
|
996
|
+
sql = "SELECT timestamp, region, value FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS WHERE region='GRID'"
|
|
997
|
+
self.cur.execute(sql + comment)
|
|
998
|
+
results = self.cur.fetchall()
|
|
999
|
+
return results
|
|
1000
|
+
|
|
1001
|
+
# set CO2 emissions
|
|
1002
|
+
def set_co2_emissions(self, panda_id, in_active=False):
|
|
1003
|
+
comment = " /* DBProxy.set_co2_emissions */"
|
|
1004
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
|
|
1005
|
+
tmp_log.debug("start")
|
|
1006
|
+
gco2_regional, gco2_global = None, None
|
|
1007
|
+
|
|
1008
|
+
# sql to get job attributes
|
|
1009
|
+
sql_read = "SELECT jediTaskID, startTime, endTime, actualCoreCount, coreCount, jobMetrics, computingSite "
|
|
1010
|
+
if in_active:
|
|
1011
|
+
sql_read += "FROM ATLAS_PANDA.jobsActive4 WHERE PandaID=:PandaID "
|
|
1012
|
+
else:
|
|
1013
|
+
sql_read += "FROM ATLAS_PANDA.jobsArchived4 WHERE PandaID=:PandaID "
|
|
1014
|
+
|
|
1015
|
+
# sql to update CO2 emissions
|
|
1016
|
+
if in_active:
|
|
1017
|
+
sql_update = "UPDATE ATLAS_PANDA.jobsActive4 "
|
|
1018
|
+
else:
|
|
1019
|
+
sql_update = "UPDATE ATLAS_PANDA.jobsArchived4 "
|
|
1020
|
+
sql_update += "SET gCO2_global=:gco2_global, gCO2_regional=:gco2_regional WHERE PandaID=:PandaID "
|
|
1021
|
+
|
|
1022
|
+
# get job attributes
|
|
1023
|
+
var_map = {":PandaID": panda_id}
|
|
1024
|
+
self.cur.execute(sql_read + comment, var_map)
|
|
1025
|
+
res_read = self.cur.fetchone()
|
|
1026
|
+
if res_read is None:
|
|
1027
|
+
tmp_log.debug("skip since job not found")
|
|
1028
|
+
else:
|
|
1029
|
+
(
|
|
1030
|
+
task_id,
|
|
1031
|
+
start_time,
|
|
1032
|
+
end_time,
|
|
1033
|
+
actual_cores,
|
|
1034
|
+
defined_cores,
|
|
1035
|
+
job_metrics,
|
|
1036
|
+
computing_site,
|
|
1037
|
+
) = res_read
|
|
1038
|
+
|
|
1039
|
+
# get core count
|
|
1040
|
+
core_count = JobUtils.getCoreCount(actual_cores, defined_cores, job_metrics)
|
|
1041
|
+
|
|
1042
|
+
# get the queues watts per core value
|
|
1043
|
+
var_map = {":panda_queue": computing_site}
|
|
1044
|
+
sql_wpc = "SELECT /* use_json_type */ scj.data.coreenergy FROM ATLAS_PANDA.schedconfig_json scj WHERE scj.panda_queue=:panda_queue"
|
|
1045
|
+
self.cur.arraysize = 100
|
|
1046
|
+
self.cur.execute(sql_wpc + comment, var_map)
|
|
1047
|
+
res_wpc = self.cur.fetchone()
|
|
1048
|
+
try:
|
|
1049
|
+
watts_per_core = float(res_wpc[0])
|
|
1050
|
+
except Exception:
|
|
1051
|
+
watts_per_core = 10
|
|
1052
|
+
tmp_log.debug(f"using watts_per_core={watts_per_core} for computing_site={computing_site}")
|
|
1053
|
+
|
|
1054
|
+
# get regional CO2 emissions
|
|
1055
|
+
co2_emissions = self.get_co2_emissions_site(computing_site)
|
|
1056
|
+
if not co2_emissions:
|
|
1057
|
+
tmp_log.debug(f"skip since co2_emissions are undefined for site={computing_site}")
|
|
1058
|
+
else:
|
|
1059
|
+
# get emitted CO2 for the job
|
|
1060
|
+
gco2_regional = JobUtils.get_job_co2(start_time, end_time, core_count, co2_emissions, watts_per_core)
|
|
1061
|
+
if gco2_regional is None:
|
|
1062
|
+
tmp_log.debug("skip since the co2 emissions could not be calculated")
|
|
1063
|
+
else:
|
|
1064
|
+
max_gco2 = 999999999
|
|
1065
|
+
gco2_regional = min(gco2_regional, max_gco2)
|
|
1066
|
+
tmp_log.debug(f"set gco2_regional={gco2_regional}")
|
|
1067
|
+
|
|
1068
|
+
# get globally averaged CO2 emissions
|
|
1069
|
+
co2_emissions = self.get_co2_emissions_grid()
|
|
1070
|
+
if not co2_emissions:
|
|
1071
|
+
tmp_log.debug("skip since co2_emissions are undefined for the grid")
|
|
1072
|
+
else:
|
|
1073
|
+
# get emitted CO2 for the job
|
|
1074
|
+
gco2_global = JobUtils.get_job_co2(start_time, end_time, core_count, co2_emissions, watts_per_core)
|
|
1075
|
+
if gco2_global is None:
|
|
1076
|
+
tmp_log.debug("skip since the co2 emissions could not be calculated")
|
|
1077
|
+
else:
|
|
1078
|
+
max_gco2 = 999999999
|
|
1079
|
+
gco2_global = min(gco2_global, max_gco2)
|
|
1080
|
+
|
|
1081
|
+
tmp_log.debug(f"set gco2_global={gco2_global}")
|
|
1082
|
+
|
|
1083
|
+
var_map = {
|
|
1084
|
+
":PandaID": panda_id,
|
|
1085
|
+
":gco2_regional": gco2_regional,
|
|
1086
|
+
":gco2_global": gco2_global,
|
|
1087
|
+
}
|
|
1088
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
1089
|
+
|
|
1090
|
+
tmp_log.debug("done")
|
|
1091
|
+
# return
|
|
1092
|
+
return gco2_regional, gco2_global
|
|
1093
|
+
|
|
1094
|
+
# get core power
|
|
1095
|
+
@memoize
|
|
1096
|
+
def get_core_power(self, site_id):
|
|
1097
|
+
comment = " /* DBProxy.get_core_power */"
|
|
1098
|
+
tmp_log = self.create_tagged_logger(comment, f"siteid={site_id}")
|
|
1099
|
+
tmp_log.debug("start")
|
|
1100
|
+
|
|
1101
|
+
sqlS = "SELECT /* use_json_type */ scj.data.corepower FROM ATLAS_PANDA.schedconfig_json scj "
|
|
1102
|
+
sqlS += "WHERE panda_queue=:siteid "
|
|
1103
|
+
|
|
1104
|
+
varMap = {":siteid": site_id}
|
|
1105
|
+
|
|
1106
|
+
try:
|
|
1107
|
+
self.cur.arraysize = 100
|
|
1108
|
+
self.cur.execute(sqlS + comment, varMap)
|
|
1109
|
+
resS = self.cur.fetchone()
|
|
1110
|
+
core_power = None
|
|
1111
|
+
if resS is not None:
|
|
1112
|
+
(core_power,) = resS
|
|
1113
|
+
core_power = float(core_power)
|
|
1114
|
+
tmp_log.debug(f"got {core_power}")
|
|
1115
|
+
return core_power, None
|
|
1116
|
+
|
|
1117
|
+
except Exception:
|
|
1118
|
+
# error
|
|
1119
|
+
self.dump_error_message(tmp_log)
|
|
1120
|
+
return None, "failed to get corePower"
|
|
1121
|
+
|
|
1122
|
+
# convert ObjID to endpoint
|
|
1123
|
+
@memoize
|
|
1124
|
+
def convertObjIDtoEndPoint(self, srcFileName, objID):
|
|
1125
|
+
comment = " /* DBProxy.convertObjIDtoEndPoint */"
|
|
1126
|
+
tmp_log = self.create_tagged_logger(comment, f"ID={objID}")
|
|
1127
|
+
tmp_log.debug("start")
|
|
1128
|
+
try:
|
|
1129
|
+
for srcFile in srcFileName.split(","):
|
|
1130
|
+
if not os.path.exists(srcFile):
|
|
1131
|
+
continue
|
|
1132
|
+
with open(srcFile) as f:
|
|
1133
|
+
data = json.load(f)
|
|
1134
|
+
for rseName in data:
|
|
1135
|
+
rseData = data[rseName]
|
|
1136
|
+
if objID in [rseData["id"], rseName]:
|
|
1137
|
+
retMap = {
|
|
1138
|
+
"name": rseName,
|
|
1139
|
+
"is_deterministic": rseData["is_deterministic"],
|
|
1140
|
+
"type": rseData["type"],
|
|
1141
|
+
}
|
|
1142
|
+
tmp_log.debug(f"got {str(retMap)}")
|
|
1143
|
+
return retMap
|
|
1144
|
+
tmp_log.debug("not found")
|
|
1145
|
+
except Exception:
|
|
1146
|
+
# error
|
|
1147
|
+
self.dump_error_message(tmp_log)
|
|
1148
|
+
return None
|
|
1149
|
+
|
|
1150
|
+
def get_config_for_pq(self, pq_name):
|
|
1151
|
+
"""
|
|
1152
|
+
Get the CRIC json configuration for a particular queue
|
|
1153
|
+
"""
|
|
1154
|
+
|
|
1155
|
+
comment = " /* DBProxy.get_config_for_pq */"
|
|
1156
|
+
tmp_log = self.create_tagged_logger(comment, pq_name)
|
|
1157
|
+
tmp_log.debug("start")
|
|
1158
|
+
|
|
1159
|
+
var_map = {":pq": pq_name}
|
|
1160
|
+
sql_get_queue_config = """
|
|
1161
|
+
SELECT data FROM ATLAS_PANDA.SCHEDCONFIG_JSON
|
|
1162
|
+
WHERE panda_queue = :pq
|
|
1163
|
+
"""
|
|
1164
|
+
tmp_v, pq_data = self.getClobObj(sql_get_queue_config + comment, var_map)
|
|
1165
|
+
if pq_data is None:
|
|
1166
|
+
tmp_log.error("Could not find queue configuration")
|
|
1167
|
+
return None
|
|
1168
|
+
|
|
1169
|
+
try:
|
|
1170
|
+
pq_data_des = pq_data[0][0]
|
|
1171
|
+
if not isinstance(pq_data_des, dict):
|
|
1172
|
+
pq_data_des = json.loads(pq_data_des)
|
|
1173
|
+
except Exception:
|
|
1174
|
+
tmp_log.error("Could not find queue configuration")
|
|
1175
|
+
return None
|
|
1176
|
+
|
|
1177
|
+
tmp_log.debug("done")
|
|
1178
|
+
return pq_data_des
|
|
1179
|
+
|
|
1180
|
+
def getQueuesInJSONSchedconfig(self):
|
|
1181
|
+
comment = " /* DBProxy.getQueuesInJSONSchedconfig */"
|
|
1182
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1183
|
+
tmp_log.debug("start")
|
|
1184
|
+
try:
|
|
1185
|
+
# sql to get workers
|
|
1186
|
+
sqlC = "SELECT /* use_json_type */ panda_queue FROM ATLAS_PANDA.schedconfig_json"
|
|
1187
|
+
# start transaction
|
|
1188
|
+
self.conn.begin()
|
|
1189
|
+
self.cur.execute(sqlC + comment)
|
|
1190
|
+
panda_queues = [row[0] for row in self.cur.fetchall()]
|
|
1191
|
+
# commit
|
|
1192
|
+
if not self._commit():
|
|
1193
|
+
raise RuntimeError("Commit error")
|
|
1194
|
+
tmp_log.debug(f"got {len(panda_queues)} queues")
|
|
1195
|
+
return panda_queues
|
|
1196
|
+
except Exception:
|
|
1197
|
+
# roll back
|
|
1198
|
+
self._rollback()
|
|
1199
|
+
# error
|
|
1200
|
+
self.dump_error_message(tmp_log)
|
|
1201
|
+
return None
|
|
1202
|
+
|
|
1203
|
+
# update queues
|
|
1204
|
+
def upsertQueuesInJSONSchedconfig(self, schedconfig_dump):
|
|
1205
|
+
comment = " /* DBProxy.upsertQueuesInJSONSchedconfig */"
|
|
1206
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1207
|
+
tmp_log.debug("start")
|
|
1208
|
+
|
|
1209
|
+
if not schedconfig_dump:
|
|
1210
|
+
tmp_log.error("empty schedconfig dump")
|
|
1211
|
+
return "ERROR"
|
|
1212
|
+
|
|
1213
|
+
try:
|
|
1214
|
+
existing_queues = self.getQueuesInJSONSchedconfig()
|
|
1215
|
+
if existing_queues is None:
|
|
1216
|
+
tmp_log.error("Could not retrieve already existing queues")
|
|
1217
|
+
return None
|
|
1218
|
+
|
|
1219
|
+
# separate the queues to the ones we have to update (existing) and the ones we have to insert (new)
|
|
1220
|
+
var_map_insert = []
|
|
1221
|
+
var_map_update = []
|
|
1222
|
+
utc_now = naive_utcnow()
|
|
1223
|
+
for pq in schedconfig_dump:
|
|
1224
|
+
data = json.dumps(schedconfig_dump[pq])
|
|
1225
|
+
if not data:
|
|
1226
|
+
tmp_log.error(f"no data for {pq}")
|
|
1227
|
+
continue
|
|
1228
|
+
|
|
1229
|
+
if pq in existing_queues:
|
|
1230
|
+
tmp_log.debug(f"pq {pq} present")
|
|
1231
|
+
var_map_update.append({":pq": pq, ":data": data, ":last_update": utc_now})
|
|
1232
|
+
else:
|
|
1233
|
+
tmp_log.debug(f"pq {pq} is new")
|
|
1234
|
+
var_map_insert.append({":pq": pq, ":data": data, ":last_update": utc_now})
|
|
1235
|
+
|
|
1236
|
+
# start transaction
|
|
1237
|
+
self.conn.begin()
|
|
1238
|
+
|
|
1239
|
+
# run the updates
|
|
1240
|
+
if var_map_update:
|
|
1241
|
+
sql_update = """
|
|
1242
|
+
UPDATE ATLAS_PANDA.SCHEDCONFIG_JSON SET data = :data, last_update = :last_update
|
|
1243
|
+
WHERE panda_queue = :pq
|
|
1244
|
+
"""
|
|
1245
|
+
tmp_log.debug("start updates")
|
|
1246
|
+
self.cur.executemany(sql_update + comment, var_map_update)
|
|
1247
|
+
tmp_log.debug("finished updates")
|
|
1248
|
+
|
|
1249
|
+
# run the inserts
|
|
1250
|
+
if var_map_insert:
|
|
1251
|
+
sql_insert = """
|
|
1252
|
+
INSERT INTO ATLAS_PANDA.SCHEDCONFIG_JSON (panda_queue, data, last_update)
|
|
1253
|
+
VALUES (:pq, :data, :last_update)
|
|
1254
|
+
"""
|
|
1255
|
+
tmp_log.debug("start inserts")
|
|
1256
|
+
self.cur.executemany(sql_insert + comment, var_map_insert)
|
|
1257
|
+
tmp_log.debug("finished inserts")
|
|
1258
|
+
|
|
1259
|
+
# delete inactive queues
|
|
1260
|
+
tmp_log.debug("Going to delete obsoleted queues")
|
|
1261
|
+
sql_delete = """
|
|
1262
|
+
DELETE FROM ATLAS_PANDA.SCHEDCONFIG_JSON WHERE last_update < current_date - INTERVAL '7' DAY
|
|
1263
|
+
"""
|
|
1264
|
+
self.cur.execute(sql_delete + comment)
|
|
1265
|
+
tmp_log.debug("deleted old queues")
|
|
1266
|
+
|
|
1267
|
+
if not self._commit():
|
|
1268
|
+
raise RuntimeError("Commit error")
|
|
1269
|
+
|
|
1270
|
+
tmp_log.debug("done")
|
|
1271
|
+
return "OK"
|
|
1272
|
+
|
|
1273
|
+
except Exception:
|
|
1274
|
+
# roll back
|
|
1275
|
+
self._rollback()
|
|
1276
|
+
self.dump_error_message(tmp_log)
|
|
1277
|
+
return "ERROR"
|
|
1278
|
+
|
|
1279
|
+
# update queues
|
|
1280
|
+
def loadSWTags(self, sw_tags):
|
|
1281
|
+
comment = " /* DBProxy.loadSWTags */"
|
|
1282
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1283
|
+
tmp_log.debug("start")
|
|
1284
|
+
|
|
1285
|
+
if not sw_tags:
|
|
1286
|
+
tmp_log.error("empty sw tag dump")
|
|
1287
|
+
return "ERROR"
|
|
1288
|
+
|
|
1289
|
+
try:
|
|
1290
|
+
var_map_tags = []
|
|
1291
|
+
|
|
1292
|
+
utc_now = naive_utcnow()
|
|
1293
|
+
for pq in sw_tags:
|
|
1294
|
+
data = sw_tags[pq]
|
|
1295
|
+
var_map_tags.append({":pq": pq, ":data": json.dumps(data), ":last_update": utc_now})
|
|
1296
|
+
|
|
1297
|
+
# start transaction on SW_TAGS table
|
|
1298
|
+
# delete everything in the table to start every time from a clean table
|
|
1299
|
+
# cleaning and filling needs to be done within the same transaction
|
|
1300
|
+
self.conn.begin()
|
|
1301
|
+
|
|
1302
|
+
sql_delete = "DELETE FROM ATLAS_PANDA.SW_TAGS"
|
|
1303
|
+
tmp_log.debug("start cleaning up SW_TAGS table")
|
|
1304
|
+
self.cur.execute(sql_delete + comment)
|
|
1305
|
+
tmp_log.debug("done cleaning up SW_TAGS table")
|
|
1306
|
+
|
|
1307
|
+
sql_insert = "INSERT INTO ATLAS_PANDA.SW_TAGS (panda_queue, data, last_update) VALUES (:pq, :data, :last_update)"
|
|
1308
|
+
tmp_log.debug("start filling up SW_TAGS table")
|
|
1309
|
+
for shard in create_shards(var_map_tags, 100): # insert in batches of 100 rows
|
|
1310
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
1311
|
+
tmp_log.debug("done filling up table")
|
|
1312
|
+
if not self._commit():
|
|
1313
|
+
raise RuntimeError("Commit error")
|
|
1314
|
+
|
|
1315
|
+
tmp_log.debug("done")
|
|
1316
|
+
return "OK"
|
|
1317
|
+
|
|
1318
|
+
except Exception:
|
|
1319
|
+
# roll back
|
|
1320
|
+
self._rollback()
|
|
1321
|
+
self.dump_error_message(tmp_log)
|
|
1322
|
+
return "ERROR"
|
|
1323
|
+
|
|
1324
|
+
# get working group with production role
|
|
1325
|
+
def getWorkingGroup(self, fqans):
|
|
1326
|
+
for fqan in fqans:
|
|
1327
|
+
# check production role
|
|
1328
|
+
match = re.search("/[^/]+/([^/]+)/Role=production", fqan)
|
|
1329
|
+
if match is not None:
|
|
1330
|
+
return match.group(1)
|
|
1331
|
+
return None
|
|
1332
|
+
|
|
1333
|
+
# update site data
|
|
1334
|
+
def updateSiteData(self, hostID, pilotRequests, interval):
|
|
1335
|
+
comment = " /* DBProxy.updateSiteData */"
|
|
1336
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1337
|
+
tmp_log.debug("start")
|
|
1338
|
+
|
|
1339
|
+
sqlDel = "DELETE FROM ATLAS_PANDAMETA.SiteData WHERE LASTMOD < :LASTMOD"
|
|
1340
|
+
|
|
1341
|
+
sqlRst = (
|
|
1342
|
+
"UPDATE ATLAS_PANDAMETA.SiteData "
|
|
1343
|
+
"SET GETJOB = :GETJOB, UPDATEJOB = :UPDATEJOB, NOJOB = :NOJOB, "
|
|
1344
|
+
"GETJOBABS = :GETJOBABS, UPDATEJOBABS = :UPDATEJOBABS, NOJOBABS = :NOJOBABS "
|
|
1345
|
+
"WHERE HOURS = :HOURS AND LASTMOD < :LASTMOD"
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
sqlCh = "SELECT * FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE FOR UPDATE NOWAIT "
|
|
1349
|
+
|
|
1350
|
+
sqlIn = (
|
|
1351
|
+
"INSERT INTO ATLAS_PANDAMETA.SiteData "
|
|
1352
|
+
"(SITE, FLAG, HOURS, GETJOB, UPDATEJOB, NOJOB, GETJOBABS, UPDATEJOBABS, NOJOBABS, "
|
|
1353
|
+
"LASTMOD, NSTART, FINISHED, FAILED, DEFINED, ASSIGNED, WAITING, ACTIVATED, HOLDING, RUNNING, TRANSFERRING) "
|
|
1354
|
+
"VALUES (:SITE, :FLAG, :HOURS, :GETJOB, :UPDATEJOB, :NOJOB, :GETJOBABS, :UPDATEJOBABS, :NOJOBABS, CURRENT_DATE, "
|
|
1355
|
+
"0, 0, 0, 0, 0, 0, 0, 0, 0, 0)"
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
sqlUp = (
|
|
1359
|
+
"UPDATE ATLAS_PANDAMETA.SiteData "
|
|
1360
|
+
"SET GETJOB = :GETJOB, UPDATEJOB = :UPDATEJOB, NOJOB = :NOJOB, "
|
|
1361
|
+
"GETJOBABS = :GETJOBABS, UPDATEJOBABS = :UPDATEJOBABS, NOJOBABS = :NOJOBABS, LASTMOD = CURRENT_DATE "
|
|
1362
|
+
"WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
sqlAll = "SELECT GETJOB, UPDATEJOB, NOJOB, GETJOBABS, UPDATEJOBABS, NOJOBABS, FLAG FROM ATLAS_PANDAMETA.SiteData WHERE HOURS = :HOURS AND SITE = :SITE"
|
|
1366
|
+
|
|
1367
|
+
try:
|
|
1368
|
+
timeNow = naive_utcnow()
|
|
1369
|
+
self.conn.begin()
|
|
1370
|
+
# delete old records
|
|
1371
|
+
varMap = {}
|
|
1372
|
+
varMap[":LASTMOD"] = timeNow - datetime.timedelta(hours=48)
|
|
1373
|
+
self.cur.execute(sqlDel + comment, varMap)
|
|
1374
|
+
# set 0 to old records
|
|
1375
|
+
varMap = {}
|
|
1376
|
+
varMap[":HOURS"] = interval
|
|
1377
|
+
varMap[":GETJOB"] = 0
|
|
1378
|
+
varMap[":UPDATEJOB"] = 0
|
|
1379
|
+
varMap[":NOJOB"] = 0
|
|
1380
|
+
varMap[":GETJOBABS"] = 0
|
|
1381
|
+
varMap[":UPDATEJOBABS"] = 0
|
|
1382
|
+
varMap[":NOJOBABS"] = 0
|
|
1383
|
+
varMap[":LASTMOD"] = timeNow - datetime.timedelta(hours=interval)
|
|
1384
|
+
self.cur.execute(sqlRst + comment, varMap)
|
|
1385
|
+
# commit
|
|
1386
|
+
if not self._commit():
|
|
1387
|
+
raise RuntimeError("Commit error")
|
|
1388
|
+
# shuffle to avoid concatenation
|
|
1389
|
+
tmpSiteList = list(pilotRequests)
|
|
1390
|
+
random.shuffle(tmpSiteList)
|
|
1391
|
+
# loop over all sites
|
|
1392
|
+
for tmpSite in tmpSiteList:
|
|
1393
|
+
tmpVal = pilotRequests[tmpSite]
|
|
1394
|
+
# start transaction
|
|
1395
|
+
self.conn.begin()
|
|
1396
|
+
# check individual host info first
|
|
1397
|
+
varMap = {}
|
|
1398
|
+
varMap[":FLAG"] = hostID
|
|
1399
|
+
varMap[":SITE"] = tmpSite
|
|
1400
|
+
varMap[":HOURS"] = interval
|
|
1401
|
+
self.cur.arraysize = 10
|
|
1402
|
+
locked = True
|
|
1403
|
+
try:
|
|
1404
|
+
# lock individual row
|
|
1405
|
+
self.cur.execute(sqlCh + comment, varMap)
|
|
1406
|
+
except Exception as e:
|
|
1407
|
+
# skip since it is being locked by another
|
|
1408
|
+
tmp_log.debug(f"skip to update {str(varMap)} due to {str(e)}")
|
|
1409
|
+
locked = False
|
|
1410
|
+
if locked:
|
|
1411
|
+
res = self.cur.fetchone()
|
|
1412
|
+
# row exists or not
|
|
1413
|
+
if res is None:
|
|
1414
|
+
sql = sqlIn
|
|
1415
|
+
else:
|
|
1416
|
+
sql = sqlUp
|
|
1417
|
+
|
|
1418
|
+
# getJob, updateJob and noJob entries contain the number of slots/nodes that submitted the request
|
|
1419
|
+
# getJobAbs, updateJobAbs and noJobAbs entries contain the absolute number of requests
|
|
1420
|
+
if "getJob" in tmpVal:
|
|
1421
|
+
varMap[":GETJOB"] = len(tmpVal["getJob"])
|
|
1422
|
+
getJobAbs = 0
|
|
1423
|
+
for node in tmpVal["getJob"]:
|
|
1424
|
+
getJobAbs += tmpVal["getJob"][node]
|
|
1425
|
+
varMap[":GETJOBABS"] = getJobAbs
|
|
1426
|
+
else:
|
|
1427
|
+
varMap[":GETJOB"] = 0
|
|
1428
|
+
varMap[":GETJOBABS"] = 0
|
|
1429
|
+
|
|
1430
|
+
if "updateJob" in tmpVal:
|
|
1431
|
+
varMap[":UPDATEJOB"] = len(tmpVal["updateJob"])
|
|
1432
|
+
updateJobAbs = 0
|
|
1433
|
+
for node in tmpVal["updateJob"]:
|
|
1434
|
+
updateJobAbs += tmpVal["updateJob"][node]
|
|
1435
|
+
varMap[":UPDATEJOBABS"] = updateJobAbs
|
|
1436
|
+
else:
|
|
1437
|
+
varMap[":UPDATEJOB"] = 0
|
|
1438
|
+
varMap[":UPDATEJOBABS"] = 0
|
|
1439
|
+
|
|
1440
|
+
if "noJob" in tmpVal:
|
|
1441
|
+
varMap[":NOJOB"] = len(tmpVal["noJob"])
|
|
1442
|
+
noJobAbs = 0
|
|
1443
|
+
for node in tmpVal["noJob"]:
|
|
1444
|
+
noJobAbs += tmpVal["noJob"][node]
|
|
1445
|
+
varMap[":NOJOBABS"] = noJobAbs
|
|
1446
|
+
else:
|
|
1447
|
+
varMap[":NOJOB"] = 0
|
|
1448
|
+
varMap[":NOJOBABS"] = 0
|
|
1449
|
+
|
|
1450
|
+
# update
|
|
1451
|
+
self.cur.execute(sql + comment, varMap)
|
|
1452
|
+
|
|
1453
|
+
# commit
|
|
1454
|
+
if not self._commit():
|
|
1455
|
+
raise RuntimeError("Commit error")
|
|
1456
|
+
|
|
1457
|
+
if locked:
|
|
1458
|
+
# start transaction
|
|
1459
|
+
self.conn.begin()
|
|
1460
|
+
# get all info
|
|
1461
|
+
sumExist = False
|
|
1462
|
+
varMap = {}
|
|
1463
|
+
varMap[":SITE"] = tmpSite
|
|
1464
|
+
varMap[":HOURS"] = interval
|
|
1465
|
+
self.cur.arraysize = 100
|
|
1466
|
+
self.cur.execute(sqlAll + comment, varMap)
|
|
1467
|
+
res = self.cur.fetchall()
|
|
1468
|
+
# get total getJob/updateJob
|
|
1469
|
+
varMap[":GETJOB"] = 0
|
|
1470
|
+
varMap[":UPDATEJOB"] = 0
|
|
1471
|
+
varMap[":NOJOB"] = 0
|
|
1472
|
+
varMap[":GETJOBABS"] = 0
|
|
1473
|
+
varMap[":UPDATEJOBABS"] = 0
|
|
1474
|
+
varMap[":NOJOBABS"] = 0
|
|
1475
|
+
nCol = 0
|
|
1476
|
+
for (
|
|
1477
|
+
tmpGetJob,
|
|
1478
|
+
tmpUpdateJob,
|
|
1479
|
+
tmpNoJob,
|
|
1480
|
+
tmpGetJobAbs,
|
|
1481
|
+
tmpUpdateJobAbs,
|
|
1482
|
+
tmpNoJobAbs,
|
|
1483
|
+
tmpFlag,
|
|
1484
|
+
) in res:
|
|
1485
|
+
# don't use summed info
|
|
1486
|
+
if tmpFlag == "production":
|
|
1487
|
+
sumExist = True
|
|
1488
|
+
continue
|
|
1489
|
+
if tmpFlag == "analysis":
|
|
1490
|
+
if tmpSite.startswith("ANALY_"):
|
|
1491
|
+
sumExist = True
|
|
1492
|
+
continue
|
|
1493
|
+
if tmpFlag in ["test"]:
|
|
1494
|
+
continue
|
|
1495
|
+
|
|
1496
|
+
if tmpGetJob is None:
|
|
1497
|
+
tmpGetJob = 0
|
|
1498
|
+
if tmpUpdateJob is None:
|
|
1499
|
+
tmpUpdateJob = 0
|
|
1500
|
+
if tmpNoJob is None:
|
|
1501
|
+
tmpNoJob = 0
|
|
1502
|
+
if tmpGetJobAbs is None:
|
|
1503
|
+
tmpGetJobAbs = 0
|
|
1504
|
+
if tmpUpdateJobAbs is None:
|
|
1505
|
+
tmpUpdateJobAbs = 0
|
|
1506
|
+
if tmpNoJobAbs is None:
|
|
1507
|
+
tmpNoJobAbs = 0
|
|
1508
|
+
|
|
1509
|
+
# sum
|
|
1510
|
+
varMap[":GETJOB"] += tmpGetJob
|
|
1511
|
+
varMap[":UPDATEJOB"] += tmpUpdateJob
|
|
1512
|
+
varMap[":NOJOB"] += tmpNoJob
|
|
1513
|
+
varMap[":GETJOBABS"] += tmpGetJobAbs
|
|
1514
|
+
varMap[":UPDATEJOBABS"] += tmpUpdateJobAbs
|
|
1515
|
+
varMap[":NOJOBABS"] += tmpNoJobAbs
|
|
1516
|
+
nCol += 1
|
|
1517
|
+
# get average
|
|
1518
|
+
if nCol != 0:
|
|
1519
|
+
if varMap[":GETJOB"] >= nCol:
|
|
1520
|
+
varMap[":GETJOB"] /= nCol
|
|
1521
|
+
if varMap[":UPDATEJOB"] >= nCol:
|
|
1522
|
+
varMap[":UPDATEJOB"] /= nCol
|
|
1523
|
+
if varMap[":NOJOB"] >= nCol:
|
|
1524
|
+
varMap[":NOJOB"] /= nCol
|
|
1525
|
+
if varMap[":GETJOBABS"] >= nCol:
|
|
1526
|
+
varMap[":GETJOBABS"] /= nCol
|
|
1527
|
+
if varMap[":UPDATEJOBABS"] >= nCol:
|
|
1528
|
+
varMap[":UPDATEJOBABS"] /= nCol
|
|
1529
|
+
if varMap[":NOJOBABS"] >= nCol:
|
|
1530
|
+
varMap[":NOJOBABS"] /= nCol
|
|
1531
|
+
|
|
1532
|
+
if tmpSite.startswith("ANALY_"):
|
|
1533
|
+
varMap[":FLAG"] = "analysis"
|
|
1534
|
+
else:
|
|
1535
|
+
varMap[":FLAG"] = "production"
|
|
1536
|
+
# row exists or not
|
|
1537
|
+
locked_sum = True
|
|
1538
|
+
if sumExist:
|
|
1539
|
+
sql = sqlUp
|
|
1540
|
+
else:
|
|
1541
|
+
sql = sqlIn
|
|
1542
|
+
# lock the summary row
|
|
1543
|
+
var_map = {k: varMap[k] for k in [":FLAG", ":SITE", ":HOURS"]}
|
|
1544
|
+
try:
|
|
1545
|
+
# lock it
|
|
1546
|
+
self.cur.execute(sqlCh + comment, var_map)
|
|
1547
|
+
except Exception as e:
|
|
1548
|
+
# skip since it is being locked by another
|
|
1549
|
+
tmp_log.debug(f"skip to update {str(var_map)} due to {str(e)}")
|
|
1550
|
+
locked_sum = False
|
|
1551
|
+
# update
|
|
1552
|
+
if locked_sum:
|
|
1553
|
+
self.cur.execute(sql + comment, varMap)
|
|
1554
|
+
tmp_log.debug(
|
|
1555
|
+
" %s hours=%s getJob=%s updateJob=%s, noJob=%s, getJobAbs=%s updateJobAbs=%s, noJobAbs=%s"
|
|
1556
|
+
% (
|
|
1557
|
+
tmpSite,
|
|
1558
|
+
interval,
|
|
1559
|
+
varMap[":GETJOB"],
|
|
1560
|
+
varMap[":UPDATEJOB"],
|
|
1561
|
+
varMap[":NOJOB"],
|
|
1562
|
+
varMap[":GETJOBABS"],
|
|
1563
|
+
varMap[":UPDATEJOBABS"],
|
|
1564
|
+
varMap[":NOJOBABS"],
|
|
1565
|
+
)
|
|
1566
|
+
)
|
|
1567
|
+
# commit
|
|
1568
|
+
if not self._commit():
|
|
1569
|
+
raise RuntimeError("Commit error")
|
|
1570
|
+
tmp_log.debug("done")
|
|
1571
|
+
return True
|
|
1572
|
+
except Exception:
|
|
1573
|
+
# roll back
|
|
1574
|
+
self._rollback()
|
|
1575
|
+
self.dump_error_message(tmp_log)
|
|
1576
|
+
return False
|
|
1577
|
+
|
|
1578
|
+
# get site data
|
|
1579
|
+
def getCurrentSiteData(self):
|
|
1580
|
+
comment = " /* DBProxy.getCurrentSiteData */"
|
|
1581
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1582
|
+
sql = "SELECT SITE,getJob,updateJob,FLAG FROM ATLAS_PANDAMETA.SiteData WHERE FLAG IN (:FLAG1,:FLAG2) and HOURS=3"
|
|
1583
|
+
varMap = {}
|
|
1584
|
+
varMap[":FLAG1"] = "production"
|
|
1585
|
+
varMap[":FLAG2"] = "analysis"
|
|
1586
|
+
try:
|
|
1587
|
+
# set autocommit on
|
|
1588
|
+
self.conn.begin()
|
|
1589
|
+
# select
|
|
1590
|
+
self.cur.arraysize = 10000
|
|
1591
|
+
self.cur.execute(sql + comment, varMap)
|
|
1592
|
+
res = self.cur.fetchall()
|
|
1593
|
+
# commit
|
|
1594
|
+
if not self._commit():
|
|
1595
|
+
raise RuntimeError("Commit error")
|
|
1596
|
+
ret = {}
|
|
1597
|
+
for site, getJob, updateJob, flag in res:
|
|
1598
|
+
if site.startswith("ANALY_"):
|
|
1599
|
+
if flag != "analysis":
|
|
1600
|
+
continue
|
|
1601
|
+
else:
|
|
1602
|
+
if flag != "production":
|
|
1603
|
+
continue
|
|
1604
|
+
ret[site] = {"getJob": getJob, "updateJob": updateJob}
|
|
1605
|
+
return ret
|
|
1606
|
+
except Exception:
|
|
1607
|
+
self.dump_error_message(tmp_log)
|
|
1608
|
+
# roll back
|
|
1609
|
+
self._rollback()
|
|
1610
|
+
return {}
|
|
1611
|
+
|
|
1612
|
+
# insert nRunning in site data
|
|
1613
|
+
def insertnRunningInSiteData(self):
|
|
1614
|
+
comment = " /* DBProxy.insertnRunningInSiteData */"
|
|
1615
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1616
|
+
tmp_log.debug("start")
|
|
1617
|
+
|
|
1618
|
+
sqlDel = "DELETE FROM ATLAS_PANDAMETA.SiteData WHERE FLAG IN (:FLAG1, :FLAG2) AND LASTMOD < CURRENT_DATE - 1"
|
|
1619
|
+
|
|
1620
|
+
sqlRun = (
|
|
1621
|
+
"SELECT COUNT(*), computingSite "
|
|
1622
|
+
"FROM ATLAS_PANDA.jobsActive4 "
|
|
1623
|
+
"WHERE prodSourceLabel IN (:prodSourceLabel1, :prodSourceLabel2) "
|
|
1624
|
+
"AND jobStatus = :jobStatus "
|
|
1625
|
+
"GROUP BY computingSite"
|
|
1626
|
+
)
|
|
1627
|
+
|
|
1628
|
+
sqlCh = "SELECT COUNT(*) FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
|
|
1629
|
+
|
|
1630
|
+
sqlIn = (
|
|
1631
|
+
"INSERT INTO ATLAS_PANDAMETA.SiteData "
|
|
1632
|
+
"(SITE, FLAG, HOURS, GETJOB, UPDATEJOB, LASTMOD, "
|
|
1633
|
+
"NSTART, FINISHED, FAILED, DEFINED, ASSIGNED, WAITING, "
|
|
1634
|
+
"ACTIVATED, HOLDING, RUNNING, TRANSFERRING) "
|
|
1635
|
+
"VALUES (:SITE, :FLAG, :HOURS, 0, 0, CURRENT_DATE, "
|
|
1636
|
+
"0, 0, 0, 0, 0, 0, 0, 0, :RUNNING, 0)"
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
sqlUp = "UPDATE ATLAS_PANDAMETA.SiteData SET RUNNING = :RUNNING, LASTMOD = CURRENT_DATE WHERE FLAG = :FLAG AND HOURS = :HOURS AND SITE = :SITE"
|
|
1640
|
+
|
|
1641
|
+
sqlMax = "SELECT SITE, MAX(RUNNING) FROM ATLAS_PANDAMETA.SiteData WHERE FLAG = :FLAG GROUP BY SITE"
|
|
1642
|
+
|
|
1643
|
+
try:
|
|
1644
|
+
# use offset(1000)+minutes for :HOURS
|
|
1645
|
+
timeNow = naive_utcnow()
|
|
1646
|
+
nHours = 1000 + timeNow.hour * 60 + timeNow.minute
|
|
1647
|
+
# delete old records
|
|
1648
|
+
varMap = {}
|
|
1649
|
+
varMap[":FLAG1"] = "max"
|
|
1650
|
+
varMap[":FLAG2"] = "snapshot"
|
|
1651
|
+
self.conn.begin()
|
|
1652
|
+
self.cur.execute(sqlDel + comment, varMap)
|
|
1653
|
+
# commit
|
|
1654
|
+
if not self._commit():
|
|
1655
|
+
raise RuntimeError("Commit error")
|
|
1656
|
+
# get nRunning
|
|
1657
|
+
varMap = {}
|
|
1658
|
+
varMap[":jobStatus"] = "running"
|
|
1659
|
+
varMap[":prodSourceLabel1"] = "user"
|
|
1660
|
+
varMap[":prodSourceLabel2"] = "panda"
|
|
1661
|
+
self.conn.begin()
|
|
1662
|
+
self.cur.arraysize = 10000
|
|
1663
|
+
self.cur.execute(sqlRun + comment, varMap)
|
|
1664
|
+
res = self.cur.fetchall()
|
|
1665
|
+
# commit
|
|
1666
|
+
if not self._commit():
|
|
1667
|
+
raise RuntimeError("Commit error")
|
|
1668
|
+
# loop over all sites
|
|
1669
|
+
for nRunning, computingSite in res:
|
|
1670
|
+
# only ANALY_ sites
|
|
1671
|
+
if not computingSite.startswith("ANALY_"):
|
|
1672
|
+
continue
|
|
1673
|
+
# check if the row is already there
|
|
1674
|
+
varMap = {}
|
|
1675
|
+
varMap[":FLAG"] = "snapshot"
|
|
1676
|
+
varMap[":SITE"] = computingSite
|
|
1677
|
+
varMap[":HOURS"] = nHours
|
|
1678
|
+
# start transaction
|
|
1679
|
+
self.conn.begin()
|
|
1680
|
+
self.cur.arraysize = 10
|
|
1681
|
+
self.cur.execute(sqlCh + comment, varMap)
|
|
1682
|
+
res = self.cur.fetchone()
|
|
1683
|
+
# row exists or not
|
|
1684
|
+
if res[0] == 0:
|
|
1685
|
+
sql = sqlIn
|
|
1686
|
+
else:
|
|
1687
|
+
sql = sqlUp
|
|
1688
|
+
# set current nRunning
|
|
1689
|
+
varMap = {}
|
|
1690
|
+
varMap[":FLAG"] = "snapshot"
|
|
1691
|
+
varMap[":SITE"] = computingSite
|
|
1692
|
+
varMap[":HOURS"] = nHours
|
|
1693
|
+
varMap[":RUNNING"] = nRunning
|
|
1694
|
+
# insert or update
|
|
1695
|
+
self.cur.execute(sql + comment, varMap)
|
|
1696
|
+
# commit
|
|
1697
|
+
if not self._commit():
|
|
1698
|
+
raise RuntimeError("Commit error")
|
|
1699
|
+
# get max nRunning
|
|
1700
|
+
varMap = {}
|
|
1701
|
+
varMap[":FLAG"] = "snapshot"
|
|
1702
|
+
self.conn.begin()
|
|
1703
|
+
self.cur.arraysize = 10000
|
|
1704
|
+
self.cur.execute(sqlMax + comment, varMap)
|
|
1705
|
+
res = self.cur.fetchall()
|
|
1706
|
+
# commit
|
|
1707
|
+
if not self._commit():
|
|
1708
|
+
raise RuntimeError("Commit error")
|
|
1709
|
+
# loop over all sites
|
|
1710
|
+
for computingSite, maxnRunning in res:
|
|
1711
|
+
# start transaction
|
|
1712
|
+
self.conn.begin()
|
|
1713
|
+
# check if the row is already there
|
|
1714
|
+
varMap = {}
|
|
1715
|
+
varMap[":FLAG"] = "max"
|
|
1716
|
+
varMap[":SITE"] = computingSite
|
|
1717
|
+
varMap[":HOURS"] = 0
|
|
1718
|
+
self.cur.arraysize = 10
|
|
1719
|
+
self.cur.execute(sqlCh + comment, varMap)
|
|
1720
|
+
res = self.cur.fetchone()
|
|
1721
|
+
# row exists or not
|
|
1722
|
+
if res[0] == 0:
|
|
1723
|
+
sql = sqlIn
|
|
1724
|
+
else:
|
|
1725
|
+
sql = sqlUp
|
|
1726
|
+
# set max nRunning
|
|
1727
|
+
varMap = {}
|
|
1728
|
+
varMap[":FLAG"] = "max"
|
|
1729
|
+
varMap[":SITE"] = computingSite
|
|
1730
|
+
varMap[":HOURS"] = 0
|
|
1731
|
+
varMap[":RUNNING"] = maxnRunning
|
|
1732
|
+
self.cur.execute(sql + comment, varMap)
|
|
1733
|
+
# commit
|
|
1734
|
+
if not self._commit():
|
|
1735
|
+
raise RuntimeError("Commit error")
|
|
1736
|
+
tmp_log.debug("done")
|
|
1737
|
+
return True
|
|
1738
|
+
except Exception:
|
|
1739
|
+
# roll back
|
|
1740
|
+
self._rollback()
|
|
1741
|
+
self.dump_error_message(tmp_log)
|
|
1742
|
+
return False
|
|
1743
|
+
|
|
1744
|
+
# get site info
|
|
1745
|
+
def getSiteInfo(self):
|
|
1746
|
+
comment = " /* DBProxy.getSiteInfo */"
|
|
1747
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
1748
|
+
tmp_log.debug("start")
|
|
1749
|
+
try:
|
|
1750
|
+
# get DDM endpoints
|
|
1751
|
+
pandaEndpointMap, endpoint_detailed_status_summary = self.getDdmEndpoints()
|
|
1752
|
+
|
|
1753
|
+
# sql to get site spec
|
|
1754
|
+
sql = """
|
|
1755
|
+
SELECT /* use_json_type */ panda_queue, data, b.site_name, c.role
|
|
1756
|
+
FROM (ATLAS_PANDA.schedconfig_json a
|
|
1757
|
+
LEFT JOIN ATLAS_PANDA.panda_site b ON a.panda_queue = b.panda_site_name)
|
|
1758
|
+
LEFT JOIN ATLAS_PANDA.site c ON b.site_name = c.site_name
|
|
1759
|
+
WHERE panda_queue IS NOT NULL
|
|
1760
|
+
"""
|
|
1761
|
+
self.cur.arraysize = 10000
|
|
1762
|
+
# self.cur.execute(sql+comment)
|
|
1763
|
+
# resList = self.cur.fetchall()
|
|
1764
|
+
ret, resList = self.getClobObj(sql, {})
|
|
1765
|
+
if not resList:
|
|
1766
|
+
tmp_log.error("Empty site list!")
|
|
1767
|
+
|
|
1768
|
+
# set autocommit on
|
|
1769
|
+
self.conn.begin()
|
|
1770
|
+
# sql to get num slots
|
|
1771
|
+
sqlSL = "SELECT pandaQueueName, gshare, resourcetype, numslots FROM ATLAS_PANDA.Harvester_Slots "
|
|
1772
|
+
sqlSL += "WHERE (expirationTime IS NULL OR expirationTime>CURRENT_DATE) "
|
|
1773
|
+
|
|
1774
|
+
num_slots_by_site = {}
|
|
1775
|
+
self.cur.execute(sqlSL + comment)
|
|
1776
|
+
resSL = self.cur.fetchall()
|
|
1777
|
+
|
|
1778
|
+
for sl_queuename, sl_gshare, sl_resourcetype, sl_numslots in resSL:
|
|
1779
|
+
if sl_numslots < 0:
|
|
1780
|
+
continue
|
|
1781
|
+
num_slots_by_site.setdefault(sl_queuename, {})
|
|
1782
|
+
num_slots_by_site[sl_queuename].setdefault(sl_gshare, {})
|
|
1783
|
+
num_slots_by_site[sl_queuename][sl_gshare][sl_resourcetype] = sl_numslots
|
|
1784
|
+
|
|
1785
|
+
retList = {}
|
|
1786
|
+
if resList is not None:
|
|
1787
|
+
# loop over all results
|
|
1788
|
+
for res in resList:
|
|
1789
|
+
try: # don't let a problem with one queue break the whole map
|
|
1790
|
+
# change None to ''
|
|
1791
|
+
resTmp = []
|
|
1792
|
+
for tmpItem in res:
|
|
1793
|
+
if tmpItem is None:
|
|
1794
|
+
tmpItem = ""
|
|
1795
|
+
resTmp.append(tmpItem)
|
|
1796
|
+
|
|
1797
|
+
siteid, queue_data_json, pandasite, role = resTmp
|
|
1798
|
+
try:
|
|
1799
|
+
if isinstance(queue_data_json, dict):
|
|
1800
|
+
queue_data = queue_data_json
|
|
1801
|
+
else:
|
|
1802
|
+
queue_data = json.loads(queue_data_json)
|
|
1803
|
+
except Exception:
|
|
1804
|
+
tmp_log.error(f"loading json for queue {siteid} excepted. json was: {queue_data_json}")
|
|
1805
|
+
continue
|
|
1806
|
+
|
|
1807
|
+
# skip invalid siteid
|
|
1808
|
+
if siteid in [None, "", "ALL"] or not queue_data:
|
|
1809
|
+
if siteid != "ALL": # skip noisy error message for ALL
|
|
1810
|
+
tmp_log.error(f"siteid {siteid} had no queue_data {queue_data}")
|
|
1811
|
+
continue
|
|
1812
|
+
|
|
1813
|
+
tmp_log.debug(f"processing queue {siteid}")
|
|
1814
|
+
|
|
1815
|
+
# instantiate SiteSpec
|
|
1816
|
+
ret = SiteSpec.SiteSpec()
|
|
1817
|
+
ret.sitename = siteid
|
|
1818
|
+
ret.pandasite = pandasite
|
|
1819
|
+
ret.role = role
|
|
1820
|
+
|
|
1821
|
+
ret.type = queue_data.get("type", "production")
|
|
1822
|
+
ret.nickname = queue_data.get("nickname")
|
|
1823
|
+
try:
|
|
1824
|
+
ret.ddm = queue_data.get("ddm", "").split(",")[0]
|
|
1825
|
+
except AttributeError:
|
|
1826
|
+
ret.ddm = ""
|
|
1827
|
+
try:
|
|
1828
|
+
ret.cloud = queue_data.get("cloud", "").split(",")[0]
|
|
1829
|
+
except AttributeError:
|
|
1830
|
+
ret.cloud = ""
|
|
1831
|
+
ret.memory = queue_data.get("memory")
|
|
1832
|
+
ret.maxrss = queue_data.get("maxrss")
|
|
1833
|
+
ret.minrss = queue_data.get("minrss")
|
|
1834
|
+
ret.maxtime = queue_data.get("maxtime")
|
|
1835
|
+
ret.status = queue_data.get("status")
|
|
1836
|
+
ret.space = queue_data.get("space")
|
|
1837
|
+
ret.maxinputsize = queue_data.get("maxinputsize")
|
|
1838
|
+
ret.comment = queue_data.get("comment_")
|
|
1839
|
+
ret.statusmodtime = queue_data.get("lastmod")
|
|
1840
|
+
ret.catchall = queue_data.get("catchall")
|
|
1841
|
+
ret.tier = queue_data.get("tier")
|
|
1842
|
+
ret.jobseed = queue_data.get("jobseed")
|
|
1843
|
+
ret.capability = queue_data.get("capability")
|
|
1844
|
+
ret.workflow = queue_data.get("workflow")
|
|
1845
|
+
ret.maxDiskio = queue_data.get("maxdiskio")
|
|
1846
|
+
ret.pandasite_state = "ACTIVE"
|
|
1847
|
+
ret.fairsharePolicy = queue_data.get("fairsharepolicy")
|
|
1848
|
+
ret.defaulttoken = queue_data.get("defaulttoken")
|
|
1849
|
+
|
|
1850
|
+
ret.direct_access_lan = queue_data.get("direct_access_lan") is True
|
|
1851
|
+
ret.direct_access_wan = queue_data.get("direct_access_wan") is True
|
|
1852
|
+
|
|
1853
|
+
ret.iscvmfs = queue_data.get("is_cvmfs") is True
|
|
1854
|
+
|
|
1855
|
+
if queue_data.get("corepower") is None:
|
|
1856
|
+
ret.corepower = 0
|
|
1857
|
+
else:
|
|
1858
|
+
ret.corepower = queue_data.get("corepower")
|
|
1859
|
+
|
|
1860
|
+
ret.wnconnectivity = queue_data.get("wnconnectivity")
|
|
1861
|
+
if ret.wnconnectivity == "":
|
|
1862
|
+
ret.wnconnectivity = None
|
|
1863
|
+
|
|
1864
|
+
# maxwdir
|
|
1865
|
+
try:
|
|
1866
|
+
if queue_data.get("maxwdir") is None:
|
|
1867
|
+
ret.maxwdir = 0
|
|
1868
|
+
else:
|
|
1869
|
+
ret.maxwdir = int(queue_data["maxwdir"])
|
|
1870
|
+
except Exception:
|
|
1871
|
+
if ret.maxinputsize in [0, None]:
|
|
1872
|
+
ret.maxwdir = 0
|
|
1873
|
+
else:
|
|
1874
|
+
try:
|
|
1875
|
+
ret.maxwdir = ret.maxinputsize + 2000
|
|
1876
|
+
except Exception:
|
|
1877
|
+
ret.maxwdir = 16336
|
|
1878
|
+
|
|
1879
|
+
# mintime
|
|
1880
|
+
if queue_data.get("mintime") is not None:
|
|
1881
|
+
ret.mintime = queue_data["mintime"]
|
|
1882
|
+
else:
|
|
1883
|
+
ret.mintime = 0
|
|
1884
|
+
|
|
1885
|
+
# reliability
|
|
1886
|
+
ret.reliabilityLevel = None
|
|
1887
|
+
|
|
1888
|
+
# pledged CPUs
|
|
1889
|
+
ret.pledgedCPU = 0
|
|
1890
|
+
if queue_data.get("pledgedcpu") not in ["", None]:
|
|
1891
|
+
try:
|
|
1892
|
+
ret.pledgedCPU = int(queue_data["pledgedcpu"])
|
|
1893
|
+
except Exception:
|
|
1894
|
+
pass
|
|
1895
|
+
|
|
1896
|
+
# core count
|
|
1897
|
+
ret.coreCount = 0
|
|
1898
|
+
if queue_data.get("corecount") not in ["", None]:
|
|
1899
|
+
try:
|
|
1900
|
+
ret.coreCount = int(queue_data["corecount"])
|
|
1901
|
+
except Exception:
|
|
1902
|
+
pass
|
|
1903
|
+
|
|
1904
|
+
# convert releases to list
|
|
1905
|
+
ret.releases = []
|
|
1906
|
+
if queue_data.get("releases"):
|
|
1907
|
+
ret.releases = queue_data["releases"]
|
|
1908
|
+
|
|
1909
|
+
# convert validatedreleases to list
|
|
1910
|
+
ret.validatedreleases = []
|
|
1911
|
+
if queue_data.get("validatedreleases"):
|
|
1912
|
+
for tmpRel in queue_data["validatedreleases"].split("|"):
|
|
1913
|
+
# remove white space
|
|
1914
|
+
tmpRel = tmpRel.strip()
|
|
1915
|
+
if tmpRel != "":
|
|
1916
|
+
ret.validatedreleases.append(tmpRel)
|
|
1917
|
+
|
|
1918
|
+
# limit of the number of transferring jobs
|
|
1919
|
+
ret.transferringlimit = 0
|
|
1920
|
+
if queue_data.get("transferringlimit") not in ["", None]:
|
|
1921
|
+
try:
|
|
1922
|
+
ret.transferringlimit = int(queue_data["transferringlimit"])
|
|
1923
|
+
except Exception:
|
|
1924
|
+
pass
|
|
1925
|
+
|
|
1926
|
+
# FAX
|
|
1927
|
+
ret.allowfax = False
|
|
1928
|
+
try:
|
|
1929
|
+
if queue_data.get("catchall") is not None and "allowfax" in queue_data["catchall"]:
|
|
1930
|
+
ret.allowfax = True
|
|
1931
|
+
if queue_data.get("allowfax") is True:
|
|
1932
|
+
ret.allowfax = True
|
|
1933
|
+
except Exception:
|
|
1934
|
+
pass
|
|
1935
|
+
|
|
1936
|
+
# DDM endpoints
|
|
1937
|
+
ret.ddm_endpoints_input = {}
|
|
1938
|
+
ret.ddm_endpoints_output = {}
|
|
1939
|
+
if siteid in pandaEndpointMap:
|
|
1940
|
+
for scope in pandaEndpointMap[siteid]:
|
|
1941
|
+
if "input" in pandaEndpointMap[siteid][scope]:
|
|
1942
|
+
ret.ddm_endpoints_input[scope] = pandaEndpointMap[siteid][scope]["input"]
|
|
1943
|
+
if "output" in pandaEndpointMap[siteid][scope]:
|
|
1944
|
+
ret.ddm_endpoints_output[scope] = pandaEndpointMap[siteid][scope]["output"]
|
|
1945
|
+
else:
|
|
1946
|
+
# empty
|
|
1947
|
+
ret.ddm_endpoints_input["default"] = DdmSpec()
|
|
1948
|
+
ret.ddm_endpoints_output["default"] = DdmSpec()
|
|
1949
|
+
|
|
1950
|
+
# initialize dictionary fields
|
|
1951
|
+
ret.setokens_input = {}
|
|
1952
|
+
ret.setokens_output = {}
|
|
1953
|
+
ret.ddm_input = {}
|
|
1954
|
+
for scope in ret.ddm_endpoints_input:
|
|
1955
|
+
# mapping between token and endpoints
|
|
1956
|
+
ret.setokens_input[scope] = ret.ddm_endpoints_input[scope].getTokenMap("input")
|
|
1957
|
+
# set DDM to the default endpoint
|
|
1958
|
+
ret.ddm_input[scope] = ret.ddm_endpoints_input[scope].getDefaultRead()
|
|
1959
|
+
|
|
1960
|
+
ret.ddm_output = {}
|
|
1961
|
+
for scope in ret.ddm_endpoints_output:
|
|
1962
|
+
# mapping between token and endpoints
|
|
1963
|
+
ret.setokens_output[scope] = ret.ddm_endpoints_output[scope].getTokenMap("output")
|
|
1964
|
+
# set DDM to the default endpoint
|
|
1965
|
+
ret.ddm_output[scope] = ret.ddm_endpoints_output[scope].getDefaultWrite()
|
|
1966
|
+
|
|
1967
|
+
# object stores
|
|
1968
|
+
try:
|
|
1969
|
+
ret.objectstores = queue_data["objectstores"]
|
|
1970
|
+
except Exception:
|
|
1971
|
+
ret.objectstores = []
|
|
1972
|
+
|
|
1973
|
+
# default unified flag
|
|
1974
|
+
ret.is_unified = False
|
|
1975
|
+
|
|
1976
|
+
# num slots
|
|
1977
|
+
ret.num_slots_map = num_slots_by_site.get(siteid, {})
|
|
1978
|
+
|
|
1979
|
+
# scale with core count
|
|
1980
|
+
if ret.use_per_core_attr() and ret.coreCount > 0:
|
|
1981
|
+
if ret.maxrss:
|
|
1982
|
+
ret.maxrss = ret.maxrss * ret.coreCount
|
|
1983
|
+
if ret.minrss:
|
|
1984
|
+
ret.minrss = ret.minrss * ret.coreCount
|
|
1985
|
+
if ret.maxwdir:
|
|
1986
|
+
ret.maxwdir = ret.maxwdir * ret.coreCount
|
|
1987
|
+
if ret.maxinputsize:
|
|
1988
|
+
ret.maxinputsize = ret.maxinputsize
|
|
1989
|
+
|
|
1990
|
+
# append
|
|
1991
|
+
retList[ret.nickname] = ret
|
|
1992
|
+
except Exception:
|
|
1993
|
+
tmp_log.error(f"exception in queue: {traceback.format_exc()}")
|
|
1994
|
+
continue
|
|
1995
|
+
# commit
|
|
1996
|
+
if not self._commit():
|
|
1997
|
+
raise RuntimeError("Commit error")
|
|
1998
|
+
tmp_log.debug("done")
|
|
1999
|
+
return retList, endpoint_detailed_status_summary
|
|
2000
|
+
except Exception as e:
|
|
2001
|
+
# roll back
|
|
2002
|
+
self._rollback()
|
|
2003
|
+
# error
|
|
2004
|
+
self.dump_error_message(tmp_log)
|
|
2005
|
+
return {}, {}
|
|
2006
|
+
|
|
2007
|
+
def getDdmEndpoints(self):
|
|
2008
|
+
"""
|
|
2009
|
+
get list of ddm input endpoints
|
|
2010
|
+
"""
|
|
2011
|
+
comment = " /* DBProxy.getDdmEndpoints */"
|
|
2012
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2013
|
+
tmp_log.debug(f"start")
|
|
2014
|
+
|
|
2015
|
+
# get all ddm endpoints
|
|
2016
|
+
sql_ddm = "SELECT * FROM ATLAS_PANDA.ddm_endpoint "
|
|
2017
|
+
self.cur.arraysize = 10000
|
|
2018
|
+
self.cur.execute(f"{sql_ddm}{comment}")
|
|
2019
|
+
results_ddm = self.cur.fetchall()
|
|
2020
|
+
|
|
2021
|
+
# extract the column names from the query
|
|
2022
|
+
column_names = [i[0].lower() for i in self.cur.description]
|
|
2023
|
+
|
|
2024
|
+
# save the endpoints into a dictionary
|
|
2025
|
+
endpoint_dict = {}
|
|
2026
|
+
detailed_status_summary = {}
|
|
2027
|
+
for ddm_endpoint_row in results_ddm:
|
|
2028
|
+
tmp_endpoint = {}
|
|
2029
|
+
# unzip the ddm_endpoint row into a dictionary
|
|
2030
|
+
for column_name, column_val in zip(column_names, ddm_endpoint_row):
|
|
2031
|
+
tmp_endpoint[column_name] = column_val
|
|
2032
|
+
|
|
2033
|
+
ddm_endpoint_name = tmp_endpoint["ddm_endpoint_name"]
|
|
2034
|
+
try:
|
|
2035
|
+
tmp_detailed_status = tmp_endpoint["detailed_status"]
|
|
2036
|
+
if not isinstance(tmp_detailed_status, dict):
|
|
2037
|
+
tmp_detailed_status = json.loads(tmp_detailed_status)
|
|
2038
|
+
# make a summary of detailed status of all endpoints
|
|
2039
|
+
if tmp_detailed_status:
|
|
2040
|
+
for tmp_activity, tmp_status in tmp_detailed_status.items():
|
|
2041
|
+
detailed_status_summary.setdefault(tmp_activity, {})
|
|
2042
|
+
detailed_status_summary[tmp_activity].setdefault(tmp_status, [])
|
|
2043
|
+
detailed_status_summary[tmp_activity][tmp_status].append(ddm_endpoint_name)
|
|
2044
|
+
tmp_endpoint["detailed_status"] = tmp_detailed_status
|
|
2045
|
+
except Exception as e:
|
|
2046
|
+
tmp_log.error(f"exception when decoding detailed_status for {ddm_endpoint_name}: {str(e)}")
|
|
2047
|
+
tmp_endpoint["detailed_status"] = {}
|
|
2048
|
+
endpoint_dict[ddm_endpoint_name] = tmp_endpoint
|
|
2049
|
+
|
|
2050
|
+
# get relationship between panda sites and ddm endpoints
|
|
2051
|
+
sql_panda_ddm = """
|
|
2052
|
+
SELECT pdr.panda_site_name, pdr.ddm_endpoint_name, pdr.is_local, de.ddm_spacetoken_name,
|
|
2053
|
+
de.is_tape, pdr.default_read, pdr.default_write, pdr.roles, pdr.order_read, pdr.order_write,
|
|
2054
|
+
nvl(pdr.scope, 'default') as scope, de.blacklisted_read
|
|
2055
|
+
FROM ATLAS_PANDA.panda_ddm_relation pdr, ATLAS_PANDA.ddm_endpoint de
|
|
2056
|
+
WHERE pdr.ddm_endpoint_name = de.ddm_endpoint_name
|
|
2057
|
+
"""
|
|
2058
|
+
if self.backend == "mysql":
|
|
2059
|
+
sql_panda_ddm = """
|
|
2060
|
+
SELECT pdr.panda_site_name, pdr.ddm_endpoint_name, pdr.is_local, de.ddm_spacetoken_name,
|
|
2061
|
+
de.is_tape, pdr.default_read, pdr.default_write, pdr.roles, pdr.order_read, pdr.order_write,
|
|
2062
|
+
ifnull(pdr.scope, 'default') as scope, de.blacklisted
|
|
2063
|
+
FROM ATLAS_PANDA.panda_ddm_relation pdr, ATLAS_PANDA.ddm_endpoint de
|
|
2064
|
+
WHERE pdr.ddm_endpoint_name = de.ddm_endpoint_name
|
|
2065
|
+
"""
|
|
2066
|
+
|
|
2067
|
+
self.cur.execute(f"{sql_panda_ddm}{comment}")
|
|
2068
|
+
results_panda_ddm = self.cur.fetchall()
|
|
2069
|
+
column_names = [i[0].lower() for i in self.cur.description]
|
|
2070
|
+
|
|
2071
|
+
# save the panda ddm relations into a dictionary
|
|
2072
|
+
panda_endpoint_map = {}
|
|
2073
|
+
for panda_ddm_row in results_panda_ddm:
|
|
2074
|
+
tmp_relation = {}
|
|
2075
|
+
for column_name, column_val in zip(column_names, panda_ddm_row):
|
|
2076
|
+
# Default unavailable endpoint space to 0
|
|
2077
|
+
if column_name.startswith("space_") and column_val is None:
|
|
2078
|
+
column_val = 0
|
|
2079
|
+
tmp_relation[column_name] = column_val
|
|
2080
|
+
|
|
2081
|
+
# add the relations to the panda endpoint map
|
|
2082
|
+
panda_site_name = tmp_relation["panda_site_name"]
|
|
2083
|
+
scope = tmp_relation["scope"]
|
|
2084
|
+
panda_endpoint_map.setdefault(panda_site_name, {})
|
|
2085
|
+
panda_endpoint_map[panda_site_name].setdefault(scope, {})
|
|
2086
|
+
|
|
2087
|
+
if panda_site_name not in panda_endpoint_map:
|
|
2088
|
+
panda_endpoint_map[panda_site_name] = {
|
|
2089
|
+
"input": DdmSpec(),
|
|
2090
|
+
"output": DdmSpec(),
|
|
2091
|
+
}
|
|
2092
|
+
if "read_lan" in tmp_relation["roles"] and tmp_relation["blacklisted_read"] != "Y":
|
|
2093
|
+
panda_endpoint_map[panda_site_name][scope].setdefault("input", DdmSpec())
|
|
2094
|
+
panda_endpoint_map[panda_site_name][scope]["input"].add(tmp_relation, endpoint_dict)
|
|
2095
|
+
if "write_lan" in tmp_relation["roles"]:
|
|
2096
|
+
panda_endpoint_map[panda_site_name][scope].setdefault("output", DdmSpec())
|
|
2097
|
+
panda_endpoint_map[panda_site_name][scope]["output"].add(tmp_relation, endpoint_dict)
|
|
2098
|
+
|
|
2099
|
+
tmp_log.debug(f"done")
|
|
2100
|
+
return panda_endpoint_map, detailed_status_summary
|
|
2101
|
+
|
|
2102
|
+
def get_cloud_list(self):
|
|
2103
|
+
"""
|
|
2104
|
+
Get a list of distinct cloud names from the database.
|
|
2105
|
+
"""
|
|
2106
|
+
comment = " /* DBProxy.get_cloud_list */"
|
|
2107
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2108
|
+
tmp_log.debug("start")
|
|
2109
|
+
try:
|
|
2110
|
+
with self.conn:
|
|
2111
|
+
sql = (
|
|
2112
|
+
f"SELECT /* use_json_type */ DISTINCT sj.data.cloud AS cloud "
|
|
2113
|
+
f"FROM {panda_config.schemaPANDA}.schedconfig_json sj "
|
|
2114
|
+
f"UNION "
|
|
2115
|
+
f"SELECT 'WORLD' AS cloud "
|
|
2116
|
+
f"FROM dual "
|
|
2117
|
+
f"ORDER BY cloud"
|
|
2118
|
+
)
|
|
2119
|
+
self.cur.arraysize = 100
|
|
2120
|
+
self.cur.execute(sql + comment)
|
|
2121
|
+
results = self.cur.fetchall()
|
|
2122
|
+
clouds = [result[0] for result in results]
|
|
2123
|
+
|
|
2124
|
+
tmp_log.debug("done")
|
|
2125
|
+
return clouds
|
|
2126
|
+
except Exception:
|
|
2127
|
+
self.dump_error_message(tmp_log)
|
|
2128
|
+
self._rollback()
|
|
2129
|
+
return []
|
|
2130
|
+
|
|
2131
|
+
# get users and groups to boost job priorities
|
|
2132
|
+
def get_dict_to_boost_job_prio(self, vo):
|
|
2133
|
+
comment = " /* DBProxy.get_dict_to_boost_job_prio */"
|
|
2134
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2135
|
+
|
|
2136
|
+
if self.job_prio_boost_dict_update_time and datetime.datetime.now(datetime.timezone.utc).replace(
|
|
2137
|
+
tzinfo=None
|
|
2138
|
+
) - self.job_prio_boost_dict_update_time < datetime.timedelta(minutes=15):
|
|
2139
|
+
return self.job_prio_boost_dict
|
|
2140
|
+
try:
|
|
2141
|
+
self.job_prio_boost_dict_update_time = naive_utcnow()
|
|
2142
|
+
self.job_prio_boost_dict = {}
|
|
2143
|
+
# get configs
|
|
2144
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2145
|
+
# get dicts
|
|
2146
|
+
res_dicts = self.getConfigValue("dbproxy", "USER_JOB_PRIO_BOOST_DICTS", "pandaserver")
|
|
2147
|
+
# parse list
|
|
2148
|
+
if res_dicts:
|
|
2149
|
+
for tmp_item in res_dicts:
|
|
2150
|
+
try:
|
|
2151
|
+
tmp_name = tmp_item["name"]
|
|
2152
|
+
tmp_type = tmp_item["type"]
|
|
2153
|
+
tmp_prio = tmp_item["prio"]
|
|
2154
|
+
tmp_expire = tmp_item.get("expire", None)
|
|
2155
|
+
# check expiration
|
|
2156
|
+
if tmp_expire:
|
|
2157
|
+
tmp_expire = datetime.datetime.strptime(tmp_expire, "%Y%m%d")
|
|
2158
|
+
if tmp_expire < naive_utcnow():
|
|
2159
|
+
continue
|
|
2160
|
+
self.job_prio_boost_dict.setdefault(tmp_type, {})
|
|
2161
|
+
self.job_prio_boost_dict[tmp_type][tmp_name] = int(tmp_prio)
|
|
2162
|
+
except Exception as e:
|
|
2163
|
+
tmp_log.error(str(e))
|
|
2164
|
+
tmp_log.debug(f"got {self.job_prio_boost_dict}")
|
|
2165
|
+
return self.job_prio_boost_dict
|
|
2166
|
+
except Exception:
|
|
2167
|
+
# roll back
|
|
2168
|
+
self._rollback()
|
|
2169
|
+
# error
|
|
2170
|
+
self.dump_error_message(tmp_log)
|
|
2171
|
+
return {}
|
|
2172
|
+
|
|
2173
|
+
# set user secret
|
|
2174
|
+
def set_user_secret(self, owner, key, value):
|
|
2175
|
+
comment = " /* DBProxy.set_user_secret */"
|
|
2176
|
+
tmp_log = self.create_tagged_logger(comment, f"owner={owner} key={key}")
|
|
2177
|
+
try:
|
|
2178
|
+
# sql to check data
|
|
2179
|
+
sqlC = "SELECT data FROM ATLAS_PANDA.Secrets WHERE owner=:owner "
|
|
2180
|
+
# sql to insert dummy
|
|
2181
|
+
sqlI = "INSERT INTO ATLAS_PANDA.Secrets (owner, updated_at) " "VALUES(:owner,CURRENT_TIMESTAMP) "
|
|
2182
|
+
# sql to update data
|
|
2183
|
+
sqlU = "UPDATE ATLAS_PANDA.Secrets SET updated_at=CURRENT_TIMESTAMP,data=:data " "WHERE owner=:owner "
|
|
2184
|
+
# start transaction
|
|
2185
|
+
self.conn.begin()
|
|
2186
|
+
# check
|
|
2187
|
+
varMap = {}
|
|
2188
|
+
varMap[":owner"] = owner
|
|
2189
|
+
tmpS, tmpR = self.getClobObj(sqlC, varMap, use_commit=False)
|
|
2190
|
+
if not tmpR:
|
|
2191
|
+
# insert dummy for new entry
|
|
2192
|
+
self.cur.execute(sqlI + comment, varMap)
|
|
2193
|
+
data = {}
|
|
2194
|
+
else:
|
|
2195
|
+
data = json.loads(tmpR[0][0])
|
|
2196
|
+
# update
|
|
2197
|
+
if key is None:
|
|
2198
|
+
# delete all
|
|
2199
|
+
data = {}
|
|
2200
|
+
elif value is None:
|
|
2201
|
+
# delete key
|
|
2202
|
+
if key in data:
|
|
2203
|
+
del data[key]
|
|
2204
|
+
else:
|
|
2205
|
+
file_key = f"___file___:{key}"
|
|
2206
|
+
if file_key in data:
|
|
2207
|
+
del data[file_key]
|
|
2208
|
+
else:
|
|
2209
|
+
data[key] = value
|
|
2210
|
+
varMap = {}
|
|
2211
|
+
varMap[":owner"] = owner
|
|
2212
|
+
varMap[":data"] = json.dumps(data)
|
|
2213
|
+
self.cur.execute(sqlU + comment, varMap)
|
|
2214
|
+
# commit
|
|
2215
|
+
if not self._commit():
|
|
2216
|
+
raise RuntimeError("Commit error")
|
|
2217
|
+
tmp_log.debug("done")
|
|
2218
|
+
return True, "OK"
|
|
2219
|
+
except Exception:
|
|
2220
|
+
# roll back
|
|
2221
|
+
self._rollback()
|
|
2222
|
+
# error
|
|
2223
|
+
self.dump_error_message(tmp_log)
|
|
2224
|
+
return False, "database error"
|
|
2225
|
+
|
|
2226
|
+
# get user secrets
|
|
2227
|
+
def get_user_secrets(self, owner, keys=None, get_json=False, use_commit=True):
|
|
2228
|
+
comment = " /* DBProxy.get_user_secrets */"
|
|
2229
|
+
tmp_log = self.create_tagged_logger(comment, f"owner={owner} keys={keys}")
|
|
2230
|
+
try:
|
|
2231
|
+
# sql to get data
|
|
2232
|
+
sqlC = "SELECT data FROM ATLAS_PANDA.Secrets WHERE owner=:owner "
|
|
2233
|
+
# check
|
|
2234
|
+
varMap = {}
|
|
2235
|
+
varMap[":owner"] = owner
|
|
2236
|
+
tmpS, tmpR = self.getClobObj(sqlC, varMap, use_commit=use_commit)
|
|
2237
|
+
if not tmpR:
|
|
2238
|
+
data = {}
|
|
2239
|
+
if not get_json:
|
|
2240
|
+
data = json.dumps({})
|
|
2241
|
+
else:
|
|
2242
|
+
data = tmpR[0][0]
|
|
2243
|
+
# return only interesting keys
|
|
2244
|
+
if keys:
|
|
2245
|
+
keys = set(keys.split(","))
|
|
2246
|
+
data = json.loads(data)
|
|
2247
|
+
for k in list(data):
|
|
2248
|
+
if k not in keys:
|
|
2249
|
+
data.pop(k)
|
|
2250
|
+
if not get_json:
|
|
2251
|
+
data = json.dumps(data)
|
|
2252
|
+
else:
|
|
2253
|
+
if get_json:
|
|
2254
|
+
data = json.loads(data)
|
|
2255
|
+
tmp_log.debug(f"got data with length={len(data)}")
|
|
2256
|
+
return True, data
|
|
2257
|
+
except Exception:
|
|
2258
|
+
# error
|
|
2259
|
+
self.dump_error_message(tmp_log)
|
|
2260
|
+
return False, "database error"
|
|
2261
|
+
|
|
2262
|
+
def configurator_write_sites(self, site_list):
|
|
2263
|
+
"""
|
|
2264
|
+
Cache the CRIC site information in the PanDA database
|
|
2265
|
+
"""
|
|
2266
|
+
comment = " /* DBProxy.configurator_write_sites */"
|
|
2267
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2268
|
+
tmp_log.debug("start")
|
|
2269
|
+
|
|
2270
|
+
try:
|
|
2271
|
+
# begin transaction
|
|
2272
|
+
self.conn.begin()
|
|
2273
|
+
|
|
2274
|
+
# get existing sites
|
|
2275
|
+
tmp_log.debug("getting existing sites")
|
|
2276
|
+
sql_get = "SELECT site_name FROM ATLAS_PANDA.site"
|
|
2277
|
+
self.cur.execute(sql_get + comment)
|
|
2278
|
+
results = self.cur.fetchall()
|
|
2279
|
+
site_name_list = list(map(lambda result: result[0], results))
|
|
2280
|
+
tmp_log.debug("finished getting existing sites")
|
|
2281
|
+
|
|
2282
|
+
# see which sites need an update and which need to be inserted new
|
|
2283
|
+
var_map_insert = []
|
|
2284
|
+
var_map_update = []
|
|
2285
|
+
for site in site_list:
|
|
2286
|
+
if site["site_name"] in site_name_list:
|
|
2287
|
+
var_map_update.append(convert_dict_to_bind_vars(site))
|
|
2288
|
+
else:
|
|
2289
|
+
var_map_insert.append(convert_dict_to_bind_vars(site))
|
|
2290
|
+
|
|
2291
|
+
tmp_log.debug("Updating sites")
|
|
2292
|
+
sql_update = "UPDATE ATLAS_PANDA.site set role=:role, tier_level=:tier_level WHERE site_name=:site_name"
|
|
2293
|
+
for shard in create_shards(var_map_update, 100):
|
|
2294
|
+
self.cur.executemany(sql_update + comment, shard)
|
|
2295
|
+
|
|
2296
|
+
tmp_log.debug("Inserting sites")
|
|
2297
|
+
sql_insert = "INSERT INTO ATLAS_PANDA.site (site_name, role, tier_level) " "VALUES(:site_name, :role, :tier_level)"
|
|
2298
|
+
for shard in create_shards(var_map_insert, 100):
|
|
2299
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
2300
|
+
|
|
2301
|
+
# commit
|
|
2302
|
+
if not self._commit():
|
|
2303
|
+
raise RuntimeError("Commit error")
|
|
2304
|
+
|
|
2305
|
+
tmp_log.debug("Done")
|
|
2306
|
+
return 0, None
|
|
2307
|
+
|
|
2308
|
+
except Exception:
|
|
2309
|
+
self._rollback()
|
|
2310
|
+
self.dump_error_message(tmp_log)
|
|
2311
|
+
return -1, None
|
|
2312
|
+
|
|
2313
|
+
def configurator_write_panda_sites(self, panda_site_list):
|
|
2314
|
+
comment = " /* DBProxy.configurator_write_panda_sites */"
|
|
2315
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2316
|
+
tmp_log.debug("start")
|
|
2317
|
+
|
|
2318
|
+
try:
|
|
2319
|
+
# begin transaction
|
|
2320
|
+
self.conn.begin()
|
|
2321
|
+
|
|
2322
|
+
# get existing panda sites
|
|
2323
|
+
tmp_log.debug("getting existing panda sites")
|
|
2324
|
+
sql_get = "SELECT panda_site_name FROM ATLAS_PANDA.panda_site"
|
|
2325
|
+
self.cur.execute(sql_get + comment)
|
|
2326
|
+
results = self.cur.fetchall()
|
|
2327
|
+
panda_site_name_list = list(map(lambda result: result[0], results))
|
|
2328
|
+
tmp_log.debug("finished getting existing panda sites")
|
|
2329
|
+
|
|
2330
|
+
# see which sites need an update and which need to be inserted new
|
|
2331
|
+
var_map_insert = []
|
|
2332
|
+
var_map_update = []
|
|
2333
|
+
for panda_site in panda_site_list:
|
|
2334
|
+
if panda_site["panda_site_name"] in panda_site_name_list:
|
|
2335
|
+
var_map_update.append(convert_dict_to_bind_vars(panda_site))
|
|
2336
|
+
else:
|
|
2337
|
+
var_map_insert.append(convert_dict_to_bind_vars(panda_site))
|
|
2338
|
+
|
|
2339
|
+
tmp_log.debug("Updating panda sites")
|
|
2340
|
+
sql_update = "UPDATE ATLAS_PANDA.panda_site set site_name=:site_name WHERE panda_site_name=:panda_site_name "
|
|
2341
|
+
for shard in create_shards(var_map_update, 100):
|
|
2342
|
+
self.cur.executemany(sql_update + comment, shard)
|
|
2343
|
+
|
|
2344
|
+
tmp_log.debug("Inserting panda sites")
|
|
2345
|
+
sql_insert = "INSERT INTO ATLAS_PANDA.panda_site (panda_site_name, site_name) " "VALUES(:panda_site_name, :site_name)"
|
|
2346
|
+
for shard in create_shards(var_map_insert, 100):
|
|
2347
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
2348
|
+
|
|
2349
|
+
# commit
|
|
2350
|
+
if not self._commit():
|
|
2351
|
+
raise RuntimeError("Commit error")
|
|
2352
|
+
|
|
2353
|
+
tmp_log.debug("Done")
|
|
2354
|
+
return 0, None
|
|
2355
|
+
|
|
2356
|
+
except Exception:
|
|
2357
|
+
self._rollback()
|
|
2358
|
+
self.dump_error_message(tmp_log)
|
|
2359
|
+
return -1, None
|
|
2360
|
+
|
|
2361
|
+
def configurator_write_ddm_endpoints(self, ddm_endpoint_list):
|
|
2362
|
+
comment = " /* DBProxy.configurator_write_ddm_endpoints */"
|
|
2363
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2364
|
+
tmp_log.debug("start")
|
|
2365
|
+
|
|
2366
|
+
try:
|
|
2367
|
+
# begin transaction
|
|
2368
|
+
self.conn.begin()
|
|
2369
|
+
|
|
2370
|
+
# get existing ddm endpoints
|
|
2371
|
+
tmp_log.debug("getting existing ddm endpoints")
|
|
2372
|
+
sql_get = "SELECT ddm_endpoint_name FROM ATLAS_PANDA.ddm_endpoint"
|
|
2373
|
+
self.cur.execute(sql_get + comment)
|
|
2374
|
+
results = self.cur.fetchall()
|
|
2375
|
+
ddm_endpoint_name_list = list(map(lambda result: result[0], results))
|
|
2376
|
+
tmp_log.debug("finished getting existing ddm endpoints")
|
|
2377
|
+
|
|
2378
|
+
# see which sites need an update and which need to be inserted new
|
|
2379
|
+
var_map_insert = []
|
|
2380
|
+
var_map_update = []
|
|
2381
|
+
for ddm_endpoint in ddm_endpoint_list:
|
|
2382
|
+
if ddm_endpoint["ddm_endpoint_name"] in ddm_endpoint_name_list:
|
|
2383
|
+
var_map_update.append(convert_dict_to_bind_vars(ddm_endpoint))
|
|
2384
|
+
else:
|
|
2385
|
+
var_map_insert.append(convert_dict_to_bind_vars(ddm_endpoint))
|
|
2386
|
+
|
|
2387
|
+
tmp_log.debug("Updating ddm endpoints")
|
|
2388
|
+
sql_update = (
|
|
2389
|
+
"UPDATE ATLAS_PANDA.ddm_endpoint set "
|
|
2390
|
+
"site_name=:site_name, ddm_spacetoken_name=:ddm_spacetoken_name, type=:type, is_tape=:is_tape, "
|
|
2391
|
+
"blacklisted=:blacklisted, blacklisted_write=:blacklisted_write, blacklisted_read=:blacklisted_read, detailed_status=:detailed_status, "
|
|
2392
|
+
"space_used=:space_used, space_free=:space_free, space_total=:space_total, space_expired=:space_expired, space_timestamp=:space_timestamp "
|
|
2393
|
+
"WHERE ddm_endpoint_name=:ddm_endpoint_name"
|
|
2394
|
+
)
|
|
2395
|
+
for shard in create_shards(var_map_update, 100):
|
|
2396
|
+
self.cur.executemany(sql_update + comment, shard)
|
|
2397
|
+
|
|
2398
|
+
tmp_log.debug("Inserting ddm endpoints")
|
|
2399
|
+
sql_insert = (
|
|
2400
|
+
"INSERT INTO ATLAS_PANDA.ddm_endpoint (ddm_endpoint_name, site_name, ddm_spacetoken_name, type, is_tape, "
|
|
2401
|
+
"blacklisted, blacklisted_write, blacklisted_read, detailed_status, "
|
|
2402
|
+
"space_used, space_free, space_total, space_expired, space_timestamp) "
|
|
2403
|
+
"VALUES(:ddm_endpoint_name, :site_name, :ddm_spacetoken_name, :type, :is_tape, "
|
|
2404
|
+
":blacklisted, :blacklisted_write, :blacklisted_read, :detailed_status, "
|
|
2405
|
+
":space_used, :space_free, :space_total, :space_expired, :space_timestamp)"
|
|
2406
|
+
)
|
|
2407
|
+
for shard in create_shards(var_map_insert, 100):
|
|
2408
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
2409
|
+
|
|
2410
|
+
# commit
|
|
2411
|
+
if not self._commit():
|
|
2412
|
+
raise RuntimeError("Commit error")
|
|
2413
|
+
|
|
2414
|
+
tmp_log.debug("Done")
|
|
2415
|
+
return 0, None
|
|
2416
|
+
|
|
2417
|
+
except Exception:
|
|
2418
|
+
self._rollback()
|
|
2419
|
+
self.dump_error_message(tmp_log)
|
|
2420
|
+
return -1, None
|
|
2421
|
+
|
|
2422
|
+
def configurator_write_panda_ddm_relations(self, relation_list):
|
|
2423
|
+
comment = " /* DBProxy.configurator_write_panda_ddm_relations */"
|
|
2424
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2425
|
+
tmp_log.debug("start")
|
|
2426
|
+
|
|
2427
|
+
try:
|
|
2428
|
+
# begin transaction
|
|
2429
|
+
self.conn.begin()
|
|
2430
|
+
|
|
2431
|
+
# Reset the relations. Important to do this inside the transaction
|
|
2432
|
+
tmp_log.debug("Deleting existing panda ddm relations")
|
|
2433
|
+
sql_delete = "DELETE FROM ATLAS_PANDA.panda_ddm_relation"
|
|
2434
|
+
self.cur.execute(sql_delete + comment)
|
|
2435
|
+
|
|
2436
|
+
var_map_insert = []
|
|
2437
|
+
for relation in relation_list:
|
|
2438
|
+
var_map_insert.append(convert_dict_to_bind_vars(relation))
|
|
2439
|
+
|
|
2440
|
+
tmp_log.debug("Inserting panda ddm relations")
|
|
2441
|
+
sql_insert = (
|
|
2442
|
+
"INSERT INTO ATLAS_PANDA.panda_ddm_relation (panda_site_name, ddm_endpoint_name, roles, "
|
|
2443
|
+
"is_local, order_read, order_write, default_read, default_write, scope) "
|
|
2444
|
+
"VALUES(:panda_site_name, :ddm_endpoint_name, :roles, "
|
|
2445
|
+
":is_local, :order_read, :order_write, :default_read, :default_write, :scope)"
|
|
2446
|
+
)
|
|
2447
|
+
for shard in create_shards(var_map_insert, 100):
|
|
2448
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
2449
|
+
|
|
2450
|
+
# commit
|
|
2451
|
+
if not self._commit():
|
|
2452
|
+
raise RuntimeError("Commit error")
|
|
2453
|
+
|
|
2454
|
+
tmp_log.debug("Done")
|
|
2455
|
+
return 0, None
|
|
2456
|
+
|
|
2457
|
+
except Exception:
|
|
2458
|
+
self._rollback()
|
|
2459
|
+
self.dump_error_message(tmp_log)
|
|
2460
|
+
return -1, None
|
|
2461
|
+
|
|
2462
|
+
def configurator_read_sites(self):
|
|
2463
|
+
comment = " /* DBProxy.configurator_read_sites */"
|
|
2464
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2465
|
+
tmp_log.debug("start")
|
|
2466
|
+
|
|
2467
|
+
try:
|
|
2468
|
+
tmp_log.debug("getting existing panda sites")
|
|
2469
|
+
sql_get = "SELECT site_name FROM ATLAS_PANDA.site"
|
|
2470
|
+
self.cur.execute(sql_get + comment)
|
|
2471
|
+
results = self.cur.fetchall()
|
|
2472
|
+
site_names = set(map(lambda result: result[0], results))
|
|
2473
|
+
tmp_log.debug("finished getting site names in configurator")
|
|
2474
|
+
|
|
2475
|
+
tmp_log.debug("Done")
|
|
2476
|
+
return site_names
|
|
2477
|
+
|
|
2478
|
+
except Exception:
|
|
2479
|
+
self.dump_error_message(tmp_log)
|
|
2480
|
+
return set()
|
|
2481
|
+
|
|
2482
|
+
def configurator_read_panda_sites(self):
|
|
2483
|
+
comment = " /* DBProxy.configurator_read_sites */"
|
|
2484
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2485
|
+
tmp_log.debug("start")
|
|
2486
|
+
|
|
2487
|
+
try:
|
|
2488
|
+
tmp_log.debug("getting existing panda sites")
|
|
2489
|
+
sql_get = "SELECT panda_site_name FROM ATLAS_PANDA.panda_site"
|
|
2490
|
+
self.cur.execute(sql_get + comment)
|
|
2491
|
+
results = self.cur.fetchall()
|
|
2492
|
+
panda_site_names = set(map(lambda result: result[0], results))
|
|
2493
|
+
tmp_log.debug("finished getting panda site names in configurator")
|
|
2494
|
+
|
|
2495
|
+
tmp_log.debug("Done")
|
|
2496
|
+
return panda_site_names
|
|
2497
|
+
|
|
2498
|
+
except Exception:
|
|
2499
|
+
self.dump_error_message(tmp_log)
|
|
2500
|
+
return set()
|
|
2501
|
+
|
|
2502
|
+
def configurator_read_ddm_endpoints(self):
|
|
2503
|
+
comment = " /* DBProxy.configurator_read_ddm_endpoints */"
|
|
2504
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2505
|
+
tmp_log.debug("start")
|
|
2506
|
+
|
|
2507
|
+
try:
|
|
2508
|
+
tmp_log.debug("getting existing ddm endpoints")
|
|
2509
|
+
sql_get = "SELECT ddm_endpoint_name FROM ATLAS_PANDA.ddm_endpoint"
|
|
2510
|
+
self.cur.execute(sql_get + comment)
|
|
2511
|
+
results = self.cur.fetchall()
|
|
2512
|
+
ddm_endpoint_names = set(map(lambda result: result[0], results))
|
|
2513
|
+
tmp_log.debug("finished getting ddm endpoint names in configurator")
|
|
2514
|
+
|
|
2515
|
+
tmp_log.debug("Done")
|
|
2516
|
+
return ddm_endpoint_names
|
|
2517
|
+
|
|
2518
|
+
except Exception:
|
|
2519
|
+
self.dump_error_message(tmp_log)
|
|
2520
|
+
return set()
|
|
2521
|
+
|
|
2522
|
+
def configurator_read_cric_sites(self):
|
|
2523
|
+
comment = " /* DBProxy.configurator_read_cric_sites */"
|
|
2524
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2525
|
+
tmp_log.debug("start")
|
|
2526
|
+
|
|
2527
|
+
try:
|
|
2528
|
+
tmp_log.debug("getting existing CRIC sites")
|
|
2529
|
+
sql_get = "SELECT /* use_json_type */ distinct scj.data.atlas_site FROM ATLAS_PANDA.schedconfig_json scj"
|
|
2530
|
+
self.cur.arraysize = 1000
|
|
2531
|
+
self.cur.execute(sql_get + comment)
|
|
2532
|
+
results = self.cur.fetchall()
|
|
2533
|
+
site_names = set(map(lambda result: result[0], results))
|
|
2534
|
+
tmp_log.debug("finished getting CRIC sites")
|
|
2535
|
+
|
|
2536
|
+
tmp_log.debug("Done")
|
|
2537
|
+
return site_names
|
|
2538
|
+
|
|
2539
|
+
except Exception:
|
|
2540
|
+
self.dump_error_message(tmp_log)
|
|
2541
|
+
return set()
|
|
2542
|
+
|
|
2543
|
+
def configurator_read_cric_panda_sites(self):
|
|
2544
|
+
comment = " /* DBProxy.configurator_read_cric_panda_sites */"
|
|
2545
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2546
|
+
tmp_log.debug("start")
|
|
2547
|
+
|
|
2548
|
+
try:
|
|
2549
|
+
tmp_log.debug("getting existing CRIC panda queues")
|
|
2550
|
+
sql_get = "SELECT panda_queue FROM ATLAS_PANDA.schedconfig_json"
|
|
2551
|
+
self.cur.execute(sql_get + comment)
|
|
2552
|
+
results = self.cur.fetchall()
|
|
2553
|
+
panda_site_names = set(map(lambda result: result[0], results))
|
|
2554
|
+
tmp_log.debug("finished getting CRIC panda queues")
|
|
2555
|
+
|
|
2556
|
+
tmp_log.debug("Done")
|
|
2557
|
+
return panda_site_names
|
|
2558
|
+
|
|
2559
|
+
except Exception:
|
|
2560
|
+
self._rollback()
|
|
2561
|
+
self.dump_error_message(tmp_log)
|
|
2562
|
+
return set()
|
|
2563
|
+
|
|
2564
|
+
def configurator_delete_sites(self, sites_to_delete):
|
|
2565
|
+
"""
|
|
2566
|
+
Delete sites and all dependent entries (panda_sites, ddm_endpoints, panda_ddm_relations).
|
|
2567
|
+
Deletion of dependent entries is done through cascade definition in models
|
|
2568
|
+
"""
|
|
2569
|
+
comment = " /* DBProxy.configurator_delete_sites */"
|
|
2570
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2571
|
+
tmp_log.debug("start")
|
|
2572
|
+
|
|
2573
|
+
if not sites_to_delete:
|
|
2574
|
+
tmp_log.debug("nothing to delete")
|
|
2575
|
+
return
|
|
2576
|
+
|
|
2577
|
+
var_map_list = list(map(lambda site_name: {":site_name": site_name}, sites_to_delete))
|
|
2578
|
+
|
|
2579
|
+
try:
|
|
2580
|
+
# begin transaction
|
|
2581
|
+
self.conn.begin()
|
|
2582
|
+
tmp_log.debug(f"deleting sites: {sites_to_delete}")
|
|
2583
|
+
sql_update = "DELETE FROM ATLAS_PANDA.site WHERE site_name=:site_name"
|
|
2584
|
+
self.cur.executemany(sql_update + comment, var_map_list)
|
|
2585
|
+
tmp_log.debug("done deleting sites")
|
|
2586
|
+
|
|
2587
|
+
# commit
|
|
2588
|
+
if not self._commit():
|
|
2589
|
+
raise RuntimeError("Commit error")
|
|
2590
|
+
|
|
2591
|
+
tmp_log.debug("Done")
|
|
2592
|
+
return 0, None
|
|
2593
|
+
|
|
2594
|
+
except Exception:
|
|
2595
|
+
self._rollback()
|
|
2596
|
+
self.dump_error_message(tmp_log)
|
|
2597
|
+
return -1, None
|
|
2598
|
+
|
|
2599
|
+
def configurator_delete_panda_sites(self, panda_sites_to_delete):
|
|
2600
|
+
"""
|
|
2601
|
+
Delete PanDA sites and dependent entries in panda_ddm_relations
|
|
2602
|
+
"""
|
|
2603
|
+
comment = " /* DBProxy.configurator_delete_panda_sites */"
|
|
2604
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2605
|
+
tmp_log.debug("start")
|
|
2606
|
+
|
|
2607
|
+
if not panda_sites_to_delete:
|
|
2608
|
+
tmp_log.debug("nothing to delete")
|
|
2609
|
+
return
|
|
2610
|
+
|
|
2611
|
+
var_map_list = list(
|
|
2612
|
+
map(
|
|
2613
|
+
lambda panda_site_name: {":panda_site_name": panda_site_name},
|
|
2614
|
+
panda_sites_to_delete,
|
|
2615
|
+
)
|
|
2616
|
+
)
|
|
2617
|
+
|
|
2618
|
+
try:
|
|
2619
|
+
# begin transaction
|
|
2620
|
+
self.conn.begin()
|
|
2621
|
+
tmp_log.debug(f"deleting panda sites: {panda_sites_to_delete}")
|
|
2622
|
+
sql_update = "DELETE FROM ATLAS_PANDA.panda_site WHERE panda_site_name=:panda_site_name"
|
|
2623
|
+
self.cur.executemany(sql_update + comment, var_map_list)
|
|
2624
|
+
tmp_log.debug("done deleting panda sites")
|
|
2625
|
+
|
|
2626
|
+
# commit
|
|
2627
|
+
if not self._commit():
|
|
2628
|
+
raise RuntimeError("Commit error")
|
|
2629
|
+
|
|
2630
|
+
tmp_log.debug("Done")
|
|
2631
|
+
return 0, None
|
|
2632
|
+
|
|
2633
|
+
except Exception:
|
|
2634
|
+
self._rollback()
|
|
2635
|
+
self.dump_error_message(tmp_log)
|
|
2636
|
+
return -1, None
|
|
2637
|
+
|
|
2638
|
+
def configurator_delete_ddm_endpoints(self, ddm_endpoints_to_delete):
|
|
2639
|
+
"""
|
|
2640
|
+
Delete DDM endpoints dependent entries in panda_ddm_relations
|
|
2641
|
+
"""
|
|
2642
|
+
comment = " /* DBProxy.configurator_delete_ddm_endpoints */"
|
|
2643
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2644
|
+
tmp_log.debug("start")
|
|
2645
|
+
|
|
2646
|
+
if not ddm_endpoints_to_delete:
|
|
2647
|
+
tmp_log.debug("nothing to delete")
|
|
2648
|
+
return
|
|
2649
|
+
|
|
2650
|
+
var_map_list = list(
|
|
2651
|
+
map(
|
|
2652
|
+
lambda ddm_endpoint_name: {":ddm_endpoint_name": ddm_endpoint_name},
|
|
2653
|
+
ddm_endpoints_to_delete,
|
|
2654
|
+
)
|
|
2655
|
+
)
|
|
2656
|
+
|
|
2657
|
+
try:
|
|
2658
|
+
# begin transaction
|
|
2659
|
+
self.conn.begin()
|
|
2660
|
+
tmp_log.debug(f"deleting ddm endpoints: {ddm_endpoints_to_delete}")
|
|
2661
|
+
sql_update = "DELETE FROM ATLAS_PANDA.ddm_endpoint WHERE ddm_endpoint_name=:ddm_endpoint_name"
|
|
2662
|
+
self.cur.executemany(sql_update + comment, var_map_list)
|
|
2663
|
+
tmp_log.debug("done deleting ddm endpoints")
|
|
2664
|
+
|
|
2665
|
+
# commit
|
|
2666
|
+
if not self._commit():
|
|
2667
|
+
raise RuntimeError("Commit error")
|
|
2668
|
+
|
|
2669
|
+
tmp_log.debug("Done")
|
|
2670
|
+
return 0, None
|
|
2671
|
+
|
|
2672
|
+
except Exception:
|
|
2673
|
+
self._rollback()
|
|
2674
|
+
self.dump_error_message(tmp_log)
|
|
2675
|
+
return -1, None
|
|
2676
|
+
|
|
2677
|
+
def carbon_write_region_emissions(self, emissions):
|
|
2678
|
+
comment = " /* DBProxy.carbon_write_regional_emissions */"
|
|
2679
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2680
|
+
tmp_log.debug("start")
|
|
2681
|
+
|
|
2682
|
+
try:
|
|
2683
|
+
# begin transaction
|
|
2684
|
+
self.conn.begin()
|
|
2685
|
+
|
|
2686
|
+
tmp_log.debug("Deleting old entries")
|
|
2687
|
+
sql_delete = "DELETE FROM ATLAS_PANDA.CARBON_REGION_EMISSIONS " "WHERE timestamp < sysdate - interval '10' day"
|
|
2688
|
+
self.cur.execute(sql_delete + comment)
|
|
2689
|
+
|
|
2690
|
+
tmp_log.debug("Inserting emissions by region")
|
|
2691
|
+
|
|
2692
|
+
sql_insert = (
|
|
2693
|
+
"INSERT /*+ ignore_row_on_dupkey_index (emissions(region, timestamp)) */ "
|
|
2694
|
+
"INTO ATLAS_PANDA.CARBON_REGION_EMISSIONS emissions (REGION, TIMESTAMP, VALUE) "
|
|
2695
|
+
"VALUES(:region, :timestamp, :value)"
|
|
2696
|
+
)
|
|
2697
|
+
for shard in create_shards(emissions, 100):
|
|
2698
|
+
self.cur.executemany(sql_insert + comment, shard)
|
|
2699
|
+
|
|
2700
|
+
# commit
|
|
2701
|
+
if not self._commit():
|
|
2702
|
+
raise RuntimeError("Commit error")
|
|
2703
|
+
|
|
2704
|
+
tmp_log.debug("Done")
|
|
2705
|
+
return 0, None
|
|
2706
|
+
|
|
2707
|
+
except Exception:
|
|
2708
|
+
self._rollback()
|
|
2709
|
+
self.dump_error_message(tmp_log)
|
|
2710
|
+
return -1, None
|
|
2711
|
+
|
|
2712
|
+
def carbon_aggregate_emissions(self):
|
|
2713
|
+
comment = " /* DBProxy.carbon_aggregate_emissions */"
|
|
2714
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
2715
|
+
tmp_log.debug("start")
|
|
2716
|
+
|
|
2717
|
+
try:
|
|
2718
|
+
# begin transaction
|
|
2719
|
+
self.conn.begin()
|
|
2720
|
+
|
|
2721
|
+
# get the percentage each region is contributing to grid computing power
|
|
2722
|
+
sql_stat = (
|
|
2723
|
+
"WITH tmp_total(total_hs) AS "
|
|
2724
|
+
"(SELECT sum(hs) "
|
|
2725
|
+
"FROM ATLAS_PANDA.jobs_share_stats) "
|
|
2726
|
+
"SELECT scj.data.region, sum(jss.hs)/tmp_total.total_hs "
|
|
2727
|
+
"FROM ATLAS_PANDA.jobs_share_stats jss, ATLAS_PANDA.schedconfig_json scj, tmp_total "
|
|
2728
|
+
"WHERE jss.computingsite = scj.panda_queue "
|
|
2729
|
+
"AND scj.data.region IS NOT NULL AND scj.data.region != 'GRID'"
|
|
2730
|
+
"GROUP BY scj.data.region, tmp_total.total_hs "
|
|
2731
|
+
)
|
|
2732
|
+
|
|
2733
|
+
region_dic = {}
|
|
2734
|
+
self.cur.arraysize = 1000
|
|
2735
|
+
stats_raw = self.cur.execute(sql_stat + comment)
|
|
2736
|
+
for entry in stats_raw:
|
|
2737
|
+
region, per_cent = entry
|
|
2738
|
+
region_dic.setdefault(region, {"emissions": 0, "per_cent": 0})
|
|
2739
|
+
region_dic[region]["per_cent"] = per_cent
|
|
2740
|
+
|
|
2741
|
+
# get the last emission values for each region
|
|
2742
|
+
sql_last = (
|
|
2743
|
+
"WITH top_ts(timestamp, region) AS "
|
|
2744
|
+
"(SELECT max(timestamp), region "
|
|
2745
|
+
"FROM atlas_panda.carbon_region_emissions "
|
|
2746
|
+
"GROUP BY region) "
|
|
2747
|
+
"SELECT cre.region, cre.value, cre.timestamp "
|
|
2748
|
+
"FROM atlas_panda.carbon_region_emissions cre, top_ts "
|
|
2749
|
+
"WHERE cre.timestamp = top_ts.timestamp AND cre.region = top_ts.region"
|
|
2750
|
+
)
|
|
2751
|
+
|
|
2752
|
+
last_emission_values = self.cur.execute(sql_last + comment)
|
|
2753
|
+
for entry in last_emission_values:
|
|
2754
|
+
region, value, ts = entry
|
|
2755
|
+
region_dic.setdefault(region, {"emissions": 0, "per_cent": 0})
|
|
2756
|
+
region_dic[region]["emissions"] = value
|
|
2757
|
+
|
|
2758
|
+
# calculate the grid average emissions
|
|
2759
|
+
average_emissions = 0
|
|
2760
|
+
for region in region_dic:
|
|
2761
|
+
tmp_log.debug(f"Region {region} with per_cent {region_dic[region]['per_cent']} and emissions {region_dic[region]['emissions']}")
|
|
2762
|
+
try:
|
|
2763
|
+
average_emissions = average_emissions + region_dic[region]["per_cent"] * region_dic[region]["emissions"]
|
|
2764
|
+
except Exception:
|
|
2765
|
+
tmp_log.debug(f"Skipped Region {region} with per_cent {region_dic[region]['per_cent']} and emissions {region_dic[region]['emissions']}")
|
|
2766
|
+
|
|
2767
|
+
# store the average emissions
|
|
2768
|
+
tmp_log.debug(f"The grid co2 emissions were averaged to {average_emissions}")
|
|
2769
|
+
utc_now = naive_utcnow()
|
|
2770
|
+
var_map = {
|
|
2771
|
+
":region": "GRID",
|
|
2772
|
+
":timestamp": utc_now,
|
|
2773
|
+
":value": average_emissions,
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
sql_insert = "INSERT INTO ATLAS_PANDA.carbon_region_emissions (region, timestamp, value) " "VALUES (:region, :timestamp, :value)"
|
|
2777
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
2778
|
+
|
|
2779
|
+
# commit
|
|
2780
|
+
if not self._commit():
|
|
2781
|
+
raise RuntimeError("Commit error")
|
|
2782
|
+
|
|
2783
|
+
tmp_log.debug("Done")
|
|
2784
|
+
return 0, None
|
|
2785
|
+
|
|
2786
|
+
except Exception:
|
|
2787
|
+
self._rollback()
|
|
2788
|
+
self.dump_error_message(tmp_log)
|
|
2789
|
+
return -1, None
|
|
2790
|
+
|
|
2791
|
+
# check quota
|
|
2792
|
+
def checkQuota(self, dn):
|
|
2793
|
+
comment = " /* DBProxy.checkQuota */"
|
|
2794
|
+
tmp_log = self.create_tagged_logger(comment, f"dn={dn}")
|
|
2795
|
+
tmp_log.debug(f"start")
|
|
2796
|
+
try:
|
|
2797
|
+
# set autocommit on
|
|
2798
|
+
self.conn.begin()
|
|
2799
|
+
# select
|
|
2800
|
+
name = CoreUtils.clean_user_id(dn)
|
|
2801
|
+
sql = "SELECT cpua1, cpua7, cpua30, quotaa1, quotaa7, quotaa30 FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
2802
|
+
varMap = {}
|
|
2803
|
+
varMap[":name"] = name
|
|
2804
|
+
self.cur.arraysize = 10
|
|
2805
|
+
self.cur.execute(sql + comment, varMap)
|
|
2806
|
+
res = self.cur.fetchall()
|
|
2807
|
+
# commit
|
|
2808
|
+
if not self._commit():
|
|
2809
|
+
raise RuntimeError("Commit error")
|
|
2810
|
+
weight = 0.0
|
|
2811
|
+
if res is not None and len(res) != 0:
|
|
2812
|
+
item = res[0]
|
|
2813
|
+
# cpu and quota
|
|
2814
|
+
cpu1 = item[0]
|
|
2815
|
+
cpu7 = item[1]
|
|
2816
|
+
cpu30 = item[2]
|
|
2817
|
+
if item[3] in [0, None]:
|
|
2818
|
+
quota1 = 0
|
|
2819
|
+
else:
|
|
2820
|
+
quota1 = item[3] * 3600
|
|
2821
|
+
if item[4] in [0, None]:
|
|
2822
|
+
quota7 = 0
|
|
2823
|
+
else:
|
|
2824
|
+
quota7 = item[4] * 3600
|
|
2825
|
+
if item[5] in [0, None]:
|
|
2826
|
+
quota30 = 0
|
|
2827
|
+
else:
|
|
2828
|
+
quota30 = item[5] * 3600
|
|
2829
|
+
# CPU usage
|
|
2830
|
+
if cpu1 is None:
|
|
2831
|
+
cpu1 = 0.0
|
|
2832
|
+
# weight
|
|
2833
|
+
if quota1 > 0:
|
|
2834
|
+
weight = float(cpu1) / float(quota1)
|
|
2835
|
+
# not exceeded the limit
|
|
2836
|
+
weight = 0.0
|
|
2837
|
+
tmp_log.debug(f"Weight:{weight} Quota:{quota1} CPU:{cpu1}")
|
|
2838
|
+
else:
|
|
2839
|
+
tmp_log.debug(f"cannot found")
|
|
2840
|
+
return weight
|
|
2841
|
+
except Exception:
|
|
2842
|
+
self.dump_error_message(tmp_log)
|
|
2843
|
+
# roll back
|
|
2844
|
+
self._rollback()
|
|
2845
|
+
return 0.0
|
|
2846
|
+
|
|
2847
|
+
# check if superuser
|
|
2848
|
+
def isSuperUser(self, userName):
|
|
2849
|
+
comment = " /* DBProxy.isSuperUser */"
|
|
2850
|
+
tmp_log = self.create_tagged_logger(comment, f"userName={userName}")
|
|
2851
|
+
tmp_log.debug("start")
|
|
2852
|
+
try:
|
|
2853
|
+
isSU = False
|
|
2854
|
+
isSG = False
|
|
2855
|
+
# start transaction
|
|
2856
|
+
self.conn.begin()
|
|
2857
|
+
# check gridpref
|
|
2858
|
+
name = CoreUtils.clean_user_id(userName)
|
|
2859
|
+
sql = "SELECT gridpref FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
2860
|
+
varMap = {}
|
|
2861
|
+
varMap[":name"] = name
|
|
2862
|
+
self.cur.arraysize = 10
|
|
2863
|
+
self.cur.execute(sql + comment, varMap)
|
|
2864
|
+
res = self.cur.fetchone()
|
|
2865
|
+
# commit
|
|
2866
|
+
if not self._commit():
|
|
2867
|
+
raise RuntimeError("Commit error")
|
|
2868
|
+
# check if s in gridpref
|
|
2869
|
+
if res is not None:
|
|
2870
|
+
(gridpref,) = res
|
|
2871
|
+
if gridpref is not None:
|
|
2872
|
+
if PrioUtil.PERMISSION_SUPER_USER in gridpref:
|
|
2873
|
+
isSU = True
|
|
2874
|
+
if PrioUtil.PERMISSION_SUPER_GROUP in gridpref:
|
|
2875
|
+
isSG = True
|
|
2876
|
+
tmp_log.debug(f"done with superUser={isSU} superGroup={isSG}")
|
|
2877
|
+
return isSU, isSG
|
|
2878
|
+
except Exception:
|
|
2879
|
+
# roll back
|
|
2880
|
+
self._rollback()
|
|
2881
|
+
# error
|
|
2882
|
+
self.dump_error_message(tmp_log)
|
|
2883
|
+
return False, False
|
|
2884
|
+
|
|
2885
|
+
# get serialize JobID and status
|
|
2886
|
+
def getUserParameter(self, dn, jobID, jobsetID):
|
|
2887
|
+
comment = " /* DBProxy.getUserParameter */"
|
|
2888
|
+
tmp_log = self.create_tagged_logger(comment, f"dn={dn} jobID={jobID} jobsetID={jobsetID}")
|
|
2889
|
+
try:
|
|
2890
|
+
# set initial values
|
|
2891
|
+
retStatus = True
|
|
2892
|
+
if jobsetID == -1:
|
|
2893
|
+
# generate new jobsetID
|
|
2894
|
+
retJobsetID = jobID
|
|
2895
|
+
# new jobID = 1 + new jobsetID
|
|
2896
|
+
retJobID = retJobsetID + 1
|
|
2897
|
+
elif jobsetID in ["NULL", None, 0]:
|
|
2898
|
+
# no jobsetID
|
|
2899
|
+
retJobsetID = None
|
|
2900
|
+
retJobID = jobID
|
|
2901
|
+
else:
|
|
2902
|
+
# user specified jobsetID
|
|
2903
|
+
retJobsetID = jobsetID
|
|
2904
|
+
retJobID = jobID
|
|
2905
|
+
# set autocommit on
|
|
2906
|
+
self.conn.begin()
|
|
2907
|
+
# select
|
|
2908
|
+
name = CoreUtils.clean_user_id(dn)
|
|
2909
|
+
sql = "SELECT jobid,status FROM ATLAS_PANDAMETA.users WHERE name=:name "
|
|
2910
|
+
sql += "FOR UPDATE "
|
|
2911
|
+
sqlAdd = "INSERT INTO ATLAS_PANDAMETA.users "
|
|
2912
|
+
sqlAdd += "(ID,NAME,LASTMOD,FIRSTJOB,LATESTJOB,CACHETIME,NCURRENT,JOBID) "
|
|
2913
|
+
sqlAdd += "VALUES(ATLAS_PANDAMETA.USERS_ID_SEQ.nextval,:name,"
|
|
2914
|
+
sqlAdd += "CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,0,1) "
|
|
2915
|
+
varMap = {}
|
|
2916
|
+
varMap[":name"] = name
|
|
2917
|
+
self.cur.execute(sql + comment, varMap)
|
|
2918
|
+
self.cur.arraysize = 10
|
|
2919
|
+
res = self.cur.fetchall()
|
|
2920
|
+
# insert if no record
|
|
2921
|
+
if res is None or len(res) == 0:
|
|
2922
|
+
try:
|
|
2923
|
+
self.cur.execute(sqlAdd + comment, varMap)
|
|
2924
|
+
retI = self.cur.rowcount
|
|
2925
|
+
tmp_log.debug(f"inserted new row with {retI}")
|
|
2926
|
+
# emulate DB response
|
|
2927
|
+
res = [[1, ""]]
|
|
2928
|
+
except Exception:
|
|
2929
|
+
self.dump_error_message(tmp_log)
|
|
2930
|
+
if res is not None and len(res) != 0:
|
|
2931
|
+
item = res[0]
|
|
2932
|
+
# JobID in DB
|
|
2933
|
+
dbJobID = item[0]
|
|
2934
|
+
# check status
|
|
2935
|
+
if item[1] in ["disabled"]:
|
|
2936
|
+
retStatus = False
|
|
2937
|
+
# use larger JobID
|
|
2938
|
+
if dbJobID >= int(retJobID) or (jobsetID == -1 and dbJobID >= int(retJobsetID)):
|
|
2939
|
+
if jobsetID == -1:
|
|
2940
|
+
# generate new jobsetID = 1 + exsiting jobID
|
|
2941
|
+
retJobsetID = dbJobID + 1
|
|
2942
|
+
# new jobID = 1 + new jobsetID
|
|
2943
|
+
retJobID = retJobsetID + 1
|
|
2944
|
+
else:
|
|
2945
|
+
# new jobID = 1 + existing jobID
|
|
2946
|
+
retJobID = dbJobID + 1
|
|
2947
|
+
# update DB
|
|
2948
|
+
varMap = {}
|
|
2949
|
+
varMap[":name"] = name
|
|
2950
|
+
varMap[":jobid"] = retJobID
|
|
2951
|
+
sql = "UPDATE ATLAS_PANDAMETA.users SET jobid=:jobid WHERE name=:name"
|
|
2952
|
+
self.cur.execute(sql + comment, varMap)
|
|
2953
|
+
tmp_log.debug(f"set JobID={retJobID}")
|
|
2954
|
+
# commit
|
|
2955
|
+
if not self._commit():
|
|
2956
|
+
raise RuntimeError("Commit error")
|
|
2957
|
+
tmp_log.debug(f"return JobID={retJobID} JobsetID={retJobsetID} Status={retStatus}")
|
|
2958
|
+
return retJobID, retJobsetID, retStatus
|
|
2959
|
+
except Exception:
|
|
2960
|
+
self.dump_error_message(tmp_log)
|
|
2961
|
+
# roll back
|
|
2962
|
+
self._rollback()
|
|
2963
|
+
return retJobID, retJobsetID, retStatus
|
|
2964
|
+
|
|
2965
|
+
# check ban user
|
|
2966
|
+
def checkBanUser(self, dn, sourceLabel, jediCheck=False):
|
|
2967
|
+
comment = " /* DBProxy.checkBanUser */"
|
|
2968
|
+
try:
|
|
2969
|
+
methodName = "checkBanUser"
|
|
2970
|
+
# set initial values
|
|
2971
|
+
retStatus = True
|
|
2972
|
+
name = CoreUtils.clean_user_id(dn)
|
|
2973
|
+
tmp_log = self.create_tagged_logger(comment, f"name={name}")
|
|
2974
|
+
tmp_log.debug(f"start dn={dn} label={sourceLabel} jediCheck={jediCheck}")
|
|
2975
|
+
# set autocommit on
|
|
2976
|
+
self.conn.begin()
|
|
2977
|
+
# select
|
|
2978
|
+
sql = "SELECT status,dn FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
2979
|
+
varMap = {}
|
|
2980
|
+
varMap[":name"] = name
|
|
2981
|
+
self.cur.execute(sql + comment, varMap)
|
|
2982
|
+
self.cur.arraysize = 10
|
|
2983
|
+
res = self.cur.fetchone()
|
|
2984
|
+
if res is not None:
|
|
2985
|
+
# check status
|
|
2986
|
+
tmpStatus, dnInDB = res
|
|
2987
|
+
if tmpStatus in ["disabled"]:
|
|
2988
|
+
retStatus = False
|
|
2989
|
+
elif jediCheck and (dnInDB in ["", None] or dnInDB != dn):
|
|
2990
|
+
# add DN
|
|
2991
|
+
sqlUp = "UPDATE ATLAS_PANDAMETA.users SET dn=:dn WHERE name=:name "
|
|
2992
|
+
varMap = {}
|
|
2993
|
+
varMap[":name"] = name
|
|
2994
|
+
varMap[":dn"] = dn
|
|
2995
|
+
self.cur.execute(sqlUp + comment, varMap)
|
|
2996
|
+
retI = self.cur.rowcount
|
|
2997
|
+
tmp_log.debug(f"update DN with Status={retI}")
|
|
2998
|
+
if retI != 1:
|
|
2999
|
+
retStatus = 1
|
|
3000
|
+
else:
|
|
3001
|
+
# new user
|
|
3002
|
+
if jediCheck:
|
|
3003
|
+
name = CoreUtils.clean_user_id(dn)
|
|
3004
|
+
sqlAdd = "INSERT INTO ATLAS_PANDAMETA.users "
|
|
3005
|
+
sqlAdd += "(ID,NAME,DN,LASTMOD,FIRSTJOB,LATESTJOB,CACHETIME,NCURRENT,JOBID) "
|
|
3006
|
+
sqlAdd += "VALUES(ATLAS_PANDAMETA.USERS_ID_SEQ.nextval,:name,:dn,"
|
|
3007
|
+
sqlAdd += "CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,CURRENT_DATE,0,1) "
|
|
3008
|
+
varMap = {}
|
|
3009
|
+
varMap[":name"] = name
|
|
3010
|
+
varMap[":dn"] = dn
|
|
3011
|
+
self.cur.execute(sqlAdd + comment, varMap)
|
|
3012
|
+
retI = self.cur.rowcount
|
|
3013
|
+
tmp_log.debug(f"inserted new row with Status={retI}")
|
|
3014
|
+
if retI != 1:
|
|
3015
|
+
retStatus = 2
|
|
3016
|
+
# commit
|
|
3017
|
+
if not self._commit():
|
|
3018
|
+
raise RuntimeError("Commit error")
|
|
3019
|
+
tmp_log.debug(f"done with Status={retStatus}")
|
|
3020
|
+
return retStatus
|
|
3021
|
+
except Exception:
|
|
3022
|
+
# roll back
|
|
3023
|
+
self._rollback()
|
|
3024
|
+
# error
|
|
3025
|
+
self.dump_error_message(tmp_log)
|
|
3026
|
+
return retStatus
|
|
3027
|
+
|
|
3028
|
+
# get email address for a user
|
|
3029
|
+
def getEmailAddr(self, name, withDN=False, withUpTime=False):
|
|
3030
|
+
comment = " /* DBProxy.getEmailAddr */"
|
|
3031
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3032
|
+
tmp_log.debug(f"get email for {name}")
|
|
3033
|
+
# sql
|
|
3034
|
+
if withDN:
|
|
3035
|
+
failedRet = "", "", None
|
|
3036
|
+
sql = "SELECT email,dn,location FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
3037
|
+
elif withUpTime:
|
|
3038
|
+
failedRet = "", None
|
|
3039
|
+
sql = "SELECT email,location FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
3040
|
+
else:
|
|
3041
|
+
failedRet = ""
|
|
3042
|
+
sql = "SELECT email FROM ATLAS_PANDAMETA.users WHERE name=:name"
|
|
3043
|
+
try:
|
|
3044
|
+
# set autocommit on
|
|
3045
|
+
self.conn.begin()
|
|
3046
|
+
# select
|
|
3047
|
+
varMap = {}
|
|
3048
|
+
varMap[":name"] = name
|
|
3049
|
+
self.cur.execute(sql + comment, varMap)
|
|
3050
|
+
self.cur.arraysize = 10
|
|
3051
|
+
res = self.cur.fetchall()
|
|
3052
|
+
# commit
|
|
3053
|
+
if not self._commit():
|
|
3054
|
+
raise RuntimeError("Commit error")
|
|
3055
|
+
if res is not None and len(res) != 0:
|
|
3056
|
+
if withDN or withUpTime:
|
|
3057
|
+
if withDN:
|
|
3058
|
+
email, dn, upTime = res[0]
|
|
3059
|
+
else:
|
|
3060
|
+
email, upTime = res[0]
|
|
3061
|
+
# convert time
|
|
3062
|
+
try:
|
|
3063
|
+
upTime = datetime.datetime.strptime(upTime, "%Y-%m-%d %H:%M:%S")
|
|
3064
|
+
except Exception:
|
|
3065
|
+
upTime = None
|
|
3066
|
+
if withDN:
|
|
3067
|
+
return email, dn, upTime
|
|
3068
|
+
else:
|
|
3069
|
+
return email, upTime
|
|
3070
|
+
else:
|
|
3071
|
+
return res[0][0]
|
|
3072
|
+
# return empty string
|
|
3073
|
+
return failedRet
|
|
3074
|
+
except Exception:
|
|
3075
|
+
self.dump_error_message(tmp_log)
|
|
3076
|
+
# roll back
|
|
3077
|
+
self._rollback()
|
|
3078
|
+
return failedRet
|
|
3079
|
+
|
|
3080
|
+
# set email address for a user
|
|
3081
|
+
def setEmailAddr(self, userName, emailAddr):
|
|
3082
|
+
comment = " /* DBProxy.setEmailAddr */"
|
|
3083
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3084
|
+
tmp_log.debug(f"{userName} to {emailAddr}")
|
|
3085
|
+
# sql
|
|
3086
|
+
sql = "UPDATE ATLAS_PANDAMETA.users SET email=:email,location=:uptime WHERE name=:name "
|
|
3087
|
+
try:
|
|
3088
|
+
# set autocommit on
|
|
3089
|
+
self.conn.begin()
|
|
3090
|
+
# set
|
|
3091
|
+
varMap = {}
|
|
3092
|
+
varMap[":name"] = userName
|
|
3093
|
+
varMap[":email"] = emailAddr
|
|
3094
|
+
varMap[":uptime"] = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
3095
|
+
self.cur.execute(sql + comment, varMap)
|
|
3096
|
+
# commit
|
|
3097
|
+
if not self._commit():
|
|
3098
|
+
raise RuntimeError("Commit error")
|
|
3099
|
+
return True
|
|
3100
|
+
except Exception:
|
|
3101
|
+
# roll back
|
|
3102
|
+
self._rollback()
|
|
3103
|
+
# error
|
|
3104
|
+
self.dump_error_message(tmp_log)
|
|
3105
|
+
return False
|
|
3106
|
+
|
|
3107
|
+
# get ban users
|
|
3108
|
+
def get_ban_users(self):
|
|
3109
|
+
comment = " /* DBProxy.get_ban_user */"
|
|
3110
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3111
|
+
tmp_log.debug("start")
|
|
3112
|
+
# sql
|
|
3113
|
+
sql = "SELECT name FROM ATLAS_PANDAMETA.users WHERE status=:status "
|
|
3114
|
+
try:
|
|
3115
|
+
# set autocommit on
|
|
3116
|
+
self.conn.begin()
|
|
3117
|
+
varMap = {}
|
|
3118
|
+
varMap[":status"] = "disabled"
|
|
3119
|
+
self.cur.execute(sql + comment, varMap)
|
|
3120
|
+
self.cur.arraysize = 10
|
|
3121
|
+
res = self.cur.fetchall()
|
|
3122
|
+
# commit
|
|
3123
|
+
if not self._commit():
|
|
3124
|
+
raise RuntimeError("Commit error")
|
|
3125
|
+
retVal = {name: False for name, in res}
|
|
3126
|
+
tmp_log.debug(f"got {retVal}")
|
|
3127
|
+
return True, retVal
|
|
3128
|
+
except Exception:
|
|
3129
|
+
# roll back
|
|
3130
|
+
self._rollback()
|
|
3131
|
+
# error
|
|
3132
|
+
self.dump_error_message(tmp_log)
|
|
3133
|
+
return False, None
|
|
3134
|
+
|
|
3135
|
+
# register token key
|
|
3136
|
+
def register_token_key(self, client_name: str, lifetime: int) -> bool:
|
|
3137
|
+
"""
|
|
3138
|
+
Register token key for a client with a lifetime and delete expired tokens
|
|
3139
|
+
|
|
3140
|
+
:param client_name: client name who owns the token key
|
|
3141
|
+
:param lifetime: lifetime of the token key in hours
|
|
3142
|
+
|
|
3143
|
+
:return: True if succeeded. False otherwise
|
|
3144
|
+
"""
|
|
3145
|
+
comment = " /* DBProxy.register_token_key */"
|
|
3146
|
+
tmp_log = self.create_tagged_logger(comment, f"client_name={client_name}")
|
|
3147
|
+
tmp_log.debug("start")
|
|
3148
|
+
try:
|
|
3149
|
+
# set autocommit on
|
|
3150
|
+
self.conn.begin()
|
|
3151
|
+
time_now = naive_utcnow()
|
|
3152
|
+
expire_at = time_now + datetime.timedelta(hours=lifetime)
|
|
3153
|
+
# check if a new key was registered recently
|
|
3154
|
+
sql = f"SELECT 1 FROM {panda_config.schemaMETA}.proxykey WHERE dn=:dn AND expires>:limit "
|
|
3155
|
+
var_map = {":dn": client_name, ":limit": expire_at - datetime.timedelta(hours=1)}
|
|
3156
|
+
self.cur.execute(sql + comment, var_map)
|
|
3157
|
+
res = self.cur.fetchone()
|
|
3158
|
+
if res:
|
|
3159
|
+
tmp_log.debug("skip as a new key was registered recently")
|
|
3160
|
+
else:
|
|
3161
|
+
# get max ID
|
|
3162
|
+
max_id = None
|
|
3163
|
+
sql = "SELECT MAX(ID) FROM ATLAS_PANDAMETA.proxykey "
|
|
3164
|
+
self.cur.execute(sql + comment, {})
|
|
3165
|
+
res = self.cur.fetchone()
|
|
3166
|
+
if res:
|
|
3167
|
+
(max_id,) = res
|
|
3168
|
+
if max_id is None:
|
|
3169
|
+
max_id = 0
|
|
3170
|
+
max_id += 1
|
|
3171
|
+
max_id %= 10000000
|
|
3172
|
+
# register a key
|
|
3173
|
+
sql = (
|
|
3174
|
+
f"INSERT INTO {panda_config.schemaMETA}.proxykey (ID,DN,CREDNAME,CREATED,EXPIRES,ORIGIN,MYPROXY) "
|
|
3175
|
+
"VALUES(:id,:dn,:credname,:created,:expires,:origin,:myproxy) "
|
|
3176
|
+
)
|
|
3177
|
+
var_map = {
|
|
3178
|
+
":id": max_id,
|
|
3179
|
+
":dn": client_name,
|
|
3180
|
+
":credname": str(uuid.uuid4()),
|
|
3181
|
+
":created": time_now,
|
|
3182
|
+
":expires": expire_at,
|
|
3183
|
+
":origin": "panda",
|
|
3184
|
+
":myproxy": "NA",
|
|
3185
|
+
}
|
|
3186
|
+
try:
|
|
3187
|
+
self.cur.execute(sql + comment, var_map)
|
|
3188
|
+
tmp_log.debug(f"registered a new key with id={max_id}")
|
|
3189
|
+
except Exception as e:
|
|
3190
|
+
# ignore ID duplication error
|
|
3191
|
+
tmp_log.debug(f"ignoring registration failure with {str(e)}")
|
|
3192
|
+
# delete obsolete keys
|
|
3193
|
+
sql = "DELETE FROM ATLAS_PANDAMETA.proxykey WHERE expires<:limit "
|
|
3194
|
+
var_map = {":limit": time_now}
|
|
3195
|
+
self.cur.execute(sql + comment, var_map)
|
|
3196
|
+
# commit
|
|
3197
|
+
if not self._commit():
|
|
3198
|
+
raise RuntimeError("Commit error")
|
|
3199
|
+
# return True
|
|
3200
|
+
tmp_log.debug("done")
|
|
3201
|
+
return True
|
|
3202
|
+
except Exception:
|
|
3203
|
+
# roll back
|
|
3204
|
+
self._rollback()
|
|
3205
|
+
# dump error
|
|
3206
|
+
self.dump_error_message(tmp_log)
|
|
3207
|
+
return False
|
|
3208
|
+
|
|
3209
|
+
# Configurator function: inserts data into the network matrix
|
|
3210
|
+
def insertNetworkMatrixData(self, data):
|
|
3211
|
+
comment = " /* DBProxy.insertNetworkMatrixData */"
|
|
3212
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3213
|
+
tmp_log.debug("start")
|
|
3214
|
+
|
|
3215
|
+
# For performance reasons we will insert the data into a temporary table
|
|
3216
|
+
# and then merge this data into the permanent table.
|
|
3217
|
+
|
|
3218
|
+
sql_insert = """
|
|
3219
|
+
INSERT INTO ATLAS_PANDA.network_matrix_kv_temp (src, dst, key, value, ts)
|
|
3220
|
+
VALUES (:src, :dst, :key, :value, :ts)
|
|
3221
|
+
"""
|
|
3222
|
+
|
|
3223
|
+
if self.backend == "postgres":
|
|
3224
|
+
sql_merge = (
|
|
3225
|
+
"INSERT INTO ATLAS_PANDA.network_matrix_kv "
|
|
3226
|
+
"(src, dst, key, value, ts) "
|
|
3227
|
+
"SELECT src, dst, key, value, ts FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP "
|
|
3228
|
+
"ON CONFLICT (src, dst, key) "
|
|
3229
|
+
"DO UPDATE SET value=EXCLUDED.value, ts=EXCLUDED.ts "
|
|
3230
|
+
)
|
|
3231
|
+
else:
|
|
3232
|
+
sql_merge = """
|
|
3233
|
+
MERGE /*+ FULL(nm_kv) */ INTO ATLAS_PANDA.network_matrix_kv nm_kv USING
|
|
3234
|
+
(SELECT src, dst, key, value, ts FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP) input
|
|
3235
|
+
ON (nm_kv.src = input.src AND nm_kv.dst= input.dst AND nm_kv.key = input.key)
|
|
3236
|
+
WHEN NOT MATCHED THEN
|
|
3237
|
+
INSERT (src, dst, key, value, ts)
|
|
3238
|
+
VALUES (input.src, input.dst, input.key, input.value, input.ts)
|
|
3239
|
+
WHEN MATCHED THEN
|
|
3240
|
+
UPDATE SET nm_kv.value = input.value, nm_kv.ts = input.ts
|
|
3241
|
+
"""
|
|
3242
|
+
try:
|
|
3243
|
+
self.conn.begin()
|
|
3244
|
+
for shard in create_shards(data, 100):
|
|
3245
|
+
time1 = time.time()
|
|
3246
|
+
var_maps = []
|
|
3247
|
+
for entry in shard:
|
|
3248
|
+
var_map = {
|
|
3249
|
+
":src": entry[0],
|
|
3250
|
+
":dst": entry[1],
|
|
3251
|
+
":key": entry[2],
|
|
3252
|
+
":value": entry[3],
|
|
3253
|
+
":ts": entry[4],
|
|
3254
|
+
}
|
|
3255
|
+
var_maps.append(var_map)
|
|
3256
|
+
|
|
3257
|
+
time2 = time.time()
|
|
3258
|
+
self.cur.executemany(sql_insert + comment, var_maps)
|
|
3259
|
+
time3 = time.time()
|
|
3260
|
+
tmp_log.debug(f"Processing a shard took: {time2 - time1}s of data preparation and {time3 - time2}s of insertion = {time3 - time1}")
|
|
3261
|
+
|
|
3262
|
+
time4 = time.time()
|
|
3263
|
+
self.cur.execute(sql_merge + comment)
|
|
3264
|
+
time5 = time.time()
|
|
3265
|
+
tmp_log.debug(f"Final merge took: {time5 - time4}s")
|
|
3266
|
+
if self.backend == "postgres":
|
|
3267
|
+
# cleanup since ON CONFLICT DO UPDATE doesn't work with duplicated entries
|
|
3268
|
+
self.cur.execute("DELETE FROM ATLAS_PANDA.NETWORK_MATRIX_KV_TEMP " + comment)
|
|
3269
|
+
# commit
|
|
3270
|
+
if not self._commit():
|
|
3271
|
+
raise RuntimeError("Commit error")
|
|
3272
|
+
|
|
3273
|
+
except Exception:
|
|
3274
|
+
# roll back
|
|
3275
|
+
self._rollback()
|
|
3276
|
+
# error
|
|
3277
|
+
self.dump_error_message(tmp_log)
|
|
3278
|
+
return None, ""
|
|
3279
|
+
|
|
3280
|
+
# Configurator function: delete old network data
|
|
3281
|
+
def deleteOldNetworkData(self):
|
|
3282
|
+
comment = " /* DBProxy.deleteOldNetworkData */"
|
|
3283
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3284
|
+
tmp_log.debug("start")
|
|
3285
|
+
|
|
3286
|
+
# delete any data older than a week
|
|
3287
|
+
sql_delete = """
|
|
3288
|
+
DELETE FROM ATLAS_PANDA.network_matrix_kv
|
|
3289
|
+
WHERE ts < (current_date - 7)
|
|
3290
|
+
"""
|
|
3291
|
+
try:
|
|
3292
|
+
self.conn.begin()
|
|
3293
|
+
time1 = time.time()
|
|
3294
|
+
self.cur.execute(sql_delete + comment)
|
|
3295
|
+
time2 = time.time()
|
|
3296
|
+
tmp_log.debug(f"Deletion of old network data took: {time2 - time1}s")
|
|
3297
|
+
|
|
3298
|
+
# commit
|
|
3299
|
+
if not self._commit():
|
|
3300
|
+
raise RuntimeError("Commit error")
|
|
3301
|
+
|
|
3302
|
+
except Exception:
|
|
3303
|
+
# roll back
|
|
3304
|
+
self._rollback()
|
|
3305
|
+
# error
|
|
3306
|
+
self.dump_error_message(tmp_log)
|
|
3307
|
+
return None, ""
|
|
3308
|
+
|
|
3309
|
+
def ups_get_queues(self):
|
|
3310
|
+
"""
|
|
3311
|
+
Identify unified pilot streaming (ups) queues: served in pull (late binding) model
|
|
3312
|
+
:return: list of panda queues
|
|
3313
|
+
"""
|
|
3314
|
+
comment = " /* DBProxy.ups_get_queues */"
|
|
3315
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3316
|
+
tmp_log.debug("start")
|
|
3317
|
+
|
|
3318
|
+
ups_queues = []
|
|
3319
|
+
sql = f"""
|
|
3320
|
+
SELECT /* use_json_type */ scj.panda_queue FROM {panda_config.schemaPANDA}.schedconfig_json scj
|
|
3321
|
+
WHERE scj.data.capability='ucore' AND scj.data.workflow = 'pull_ups'
|
|
3322
|
+
"""
|
|
3323
|
+
|
|
3324
|
+
self.cur.execute(sql + comment)
|
|
3325
|
+
res = self.cur.fetchall()
|
|
3326
|
+
for (ups_queue,) in res:
|
|
3327
|
+
ups_queues.append(ups_queue)
|
|
3328
|
+
|
|
3329
|
+
tmp_log.debug("done")
|
|
3330
|
+
return ups_queues
|
|
3331
|
+
|
|
3332
|
+
# calculate RW for tasks
|
|
3333
|
+
def calculateTaskRW_JEDI(self, jediTaskID):
|
|
3334
|
+
comment = " /* JediDBProxy.calculateTaskRW_JEDI */"
|
|
3335
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
3336
|
+
tmpLog.debug("start")
|
|
3337
|
+
try:
|
|
3338
|
+
# sql to get RW
|
|
3339
|
+
sql = "SELECT ROUND(SUM((nFiles-nFilesFinished-nFilesFailed-nFilesOnHold)*walltime)/24/3600) "
|
|
3340
|
+
sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD ".format(panda_config.schemaJEDI)
|
|
3341
|
+
sql += "WHERE tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
|
|
3342
|
+
sql += "AND tabT.jediTaskID=:jediTaskID "
|
|
3343
|
+
varMap = {}
|
|
3344
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
3345
|
+
# begin transaction
|
|
3346
|
+
self.conn.begin()
|
|
3347
|
+
# get
|
|
3348
|
+
self.cur.execute(sql + comment, varMap)
|
|
3349
|
+
resRT = self.cur.fetchone()
|
|
3350
|
+
# commit
|
|
3351
|
+
if not self._commit():
|
|
3352
|
+
raise RuntimeError("Commit error")
|
|
3353
|
+
# locked by another
|
|
3354
|
+
if resRT is None:
|
|
3355
|
+
retVal = None
|
|
3356
|
+
else:
|
|
3357
|
+
retVal = resRT[0]
|
|
3358
|
+
tmpLog.debug(f"RW={retVal}")
|
|
3359
|
+
# return
|
|
3360
|
+
tmpLog.debug("done")
|
|
3361
|
+
return retVal
|
|
3362
|
+
except Exception:
|
|
3363
|
+
# roll back
|
|
3364
|
+
self._rollback()
|
|
3365
|
+
# error
|
|
3366
|
+
self.dump_error_message(tmpLog)
|
|
3367
|
+
return None
|
|
3368
|
+
|
|
3369
|
+
# calculate RW with a priority
|
|
3370
|
+
def calculateRWwithPrio_JEDI(self, vo, prodSourceLabel, workQueue, priority):
|
|
3371
|
+
comment = " /* JediDBProxy.calculateRWwithPrio_JEDI */"
|
|
3372
|
+
if workQueue is None:
|
|
3373
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={None} prio={priority}")
|
|
3374
|
+
else:
|
|
3375
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={workQueue.queue_name} prio={priority}")
|
|
3376
|
+
tmpLog.debug("start")
|
|
3377
|
+
try:
|
|
3378
|
+
# sql to get RW
|
|
3379
|
+
varMap = {}
|
|
3380
|
+
varMap[":vo"] = vo
|
|
3381
|
+
varMap[":prodSourceLabel"] = prodSourceLabel
|
|
3382
|
+
if priority is not None:
|
|
3383
|
+
varMap[":priority"] = priority
|
|
3384
|
+
sql = "SELECT tabT.jediTaskID,tabT.cloud,tabD.datasetID,nFiles-nFilesFinished-nFilesFailed,walltime "
|
|
3385
|
+
sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD,{0}.JEDI_AUX_Status_MinTaskID tabA ".format(panda_config.schemaJEDI)
|
|
3386
|
+
sql += "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID "
|
|
3387
|
+
sql += "AND tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
|
|
3388
|
+
sql += "AND (nFiles-nFilesFinished-nFilesFailed)>0 "
|
|
3389
|
+
sql += "AND tabT.vo=:vo AND prodSourceLabel=:prodSourceLabel "
|
|
3390
|
+
|
|
3391
|
+
if priority is not None:
|
|
3392
|
+
sql += "AND currentPriority>=:priority "
|
|
3393
|
+
|
|
3394
|
+
if workQueue is not None:
|
|
3395
|
+
if workQueue.is_global_share:
|
|
3396
|
+
sql += "AND gshare=:wq_name "
|
|
3397
|
+
sql += f"AND tabT.workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaJEDI}.jedi_work_queue WHERE queue_function = 'Resource') "
|
|
3398
|
+
varMap[":wq_name"] = workQueue.queue_name
|
|
3399
|
+
else:
|
|
3400
|
+
sql += "AND workQueue_ID=:wq_id "
|
|
3401
|
+
varMap[":wq_id"] = workQueue.queue_id
|
|
3402
|
+
|
|
3403
|
+
sql += "AND tabT.status IN (:status1,:status2,:status3,:status4) "
|
|
3404
|
+
sql += f"AND tabD.type IN ({INPUT_TYPES_var_str}) "
|
|
3405
|
+
varMap.update(INPUT_TYPES_var_map)
|
|
3406
|
+
varMap[":status1"] = "ready"
|
|
3407
|
+
varMap[":status2"] = "scouting"
|
|
3408
|
+
varMap[":status3"] = "running"
|
|
3409
|
+
varMap[":status4"] = "pending"
|
|
3410
|
+
sql += "AND tabT.cloud IS NOT NULL "
|
|
3411
|
+
# begin transaction
|
|
3412
|
+
self.conn.begin()
|
|
3413
|
+
# set cloud
|
|
3414
|
+
self.cur.execute(sql + comment, varMap)
|
|
3415
|
+
resList = self.cur.fetchall()
|
|
3416
|
+
# commit
|
|
3417
|
+
if not self._commit():
|
|
3418
|
+
raise RuntimeError("Commit error")
|
|
3419
|
+
# loop over all tasks
|
|
3420
|
+
retMap = {}
|
|
3421
|
+
sqlF = "SELECT fsize,startEvent,endEvent,nEvents "
|
|
3422
|
+
sqlF += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
|
|
3423
|
+
sqlF += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND rownum<=1"
|
|
3424
|
+
for jediTaskID, cloud, datasetID, nRem, walltime in resList:
|
|
3425
|
+
# get effective size
|
|
3426
|
+
varMap = {}
|
|
3427
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
3428
|
+
varMap[":datasetID"] = datasetID
|
|
3429
|
+
# begin transaction
|
|
3430
|
+
self.conn.begin()
|
|
3431
|
+
# get file
|
|
3432
|
+
self.cur.execute(sqlF + comment, varMap)
|
|
3433
|
+
resFile = self.cur.fetchone()
|
|
3434
|
+
# commit
|
|
3435
|
+
if not self._commit():
|
|
3436
|
+
raise RuntimeError("Commit error")
|
|
3437
|
+
if resFile is not None:
|
|
3438
|
+
# calculate RW using effective size
|
|
3439
|
+
fsize, startEvent, endEvent, nEvents = resFile
|
|
3440
|
+
effectiveFsize = CoreUtils.getEffectiveFileSize(fsize, startEvent, endEvent, nEvents)
|
|
3441
|
+
tmpRW = nRem * effectiveFsize * walltime
|
|
3442
|
+
if cloud not in retMap:
|
|
3443
|
+
retMap[cloud] = 0
|
|
3444
|
+
retMap[cloud] += tmpRW
|
|
3445
|
+
for cloudName, rwValue in retMap.items():
|
|
3446
|
+
retMap[cloudName] = int(rwValue / 24 / 3600)
|
|
3447
|
+
tmpLog.debug(f"RW={str(retMap)}")
|
|
3448
|
+
# return
|
|
3449
|
+
tmpLog.debug("done")
|
|
3450
|
+
return retMap
|
|
3451
|
+
except Exception:
|
|
3452
|
+
# roll back
|
|
3453
|
+
self._rollback()
|
|
3454
|
+
# error
|
|
3455
|
+
self.dump_error_message(tmpLog)
|
|
3456
|
+
return None
|
|
3457
|
+
|
|
3458
|
+
# calculate WORLD RW with a priority
|
|
3459
|
+
def calculateWorldRWwithPrio_JEDI(self, vo, prodSourceLabel, workQueue, priority):
|
|
3460
|
+
comment = " /* JediDBProxy.calculateWorldRWwithPrio_JEDI */"
|
|
3461
|
+
if workQueue is None:
|
|
3462
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={None} prio={priority}")
|
|
3463
|
+
else:
|
|
3464
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} queue={workQueue.queue_name} prio={priority}")
|
|
3465
|
+
tmpLog.debug("start")
|
|
3466
|
+
try:
|
|
3467
|
+
# sql to get RW
|
|
3468
|
+
varMap = {}
|
|
3469
|
+
varMap[":vo"] = vo
|
|
3470
|
+
varMap[":prodSourceLabel"] = prodSourceLabel
|
|
3471
|
+
varMap[":worldCloud"] = JediTaskSpec.worldCloudName
|
|
3472
|
+
if priority is not None:
|
|
3473
|
+
varMap[":priority"] = priority
|
|
3474
|
+
sql = "SELECT tabT.nucleus,SUM((nEvents-nEventsUsed)*(CASE WHEN cpuTime IS NULL THEN 300 ELSE cpuTime END)) "
|
|
3475
|
+
sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD,{0}.JEDI_AUX_Status_MinTaskID tabA ".format(panda_config.schemaJEDI)
|
|
3476
|
+
sql += "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID "
|
|
3477
|
+
sql += "AND tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
|
|
3478
|
+
sql += "AND (nFiles-nFilesFinished-nFilesFailed)>0 "
|
|
3479
|
+
sql += "AND tabT.vo=:vo AND prodSourceLabel=:prodSourceLabel "
|
|
3480
|
+
sql += "AND tabT.cloud=:worldCloud "
|
|
3481
|
+
|
|
3482
|
+
if priority is not None:
|
|
3483
|
+
sql += "AND currentPriority>=:priority "
|
|
3484
|
+
|
|
3485
|
+
if workQueue is not None:
|
|
3486
|
+
if workQueue.is_global_share:
|
|
3487
|
+
sql += "AND gshare=:wq_name "
|
|
3488
|
+
sql += f"AND tabT.workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaJEDI}.jedi_work_queue WHERE queue_function = 'Resource') "
|
|
3489
|
+
varMap[":wq_name"] = workQueue.queue_name
|
|
3490
|
+
else:
|
|
3491
|
+
sql += "AND workQueue_ID=:wq_id "
|
|
3492
|
+
varMap[":wq_id"] = workQueue.queue_id
|
|
3493
|
+
|
|
3494
|
+
sql += "AND tabT.status IN (:status1,:status2,:status3,:status4) "
|
|
3495
|
+
sql += f"AND tabD.type IN ({INPUT_TYPES_var_str}) "
|
|
3496
|
+
varMap.update(INPUT_TYPES_var_map)
|
|
3497
|
+
varMap[":status1"] = "ready"
|
|
3498
|
+
varMap[":status2"] = "scouting"
|
|
3499
|
+
varMap[":status3"] = "running"
|
|
3500
|
+
varMap[":status4"] = "pending"
|
|
3501
|
+
sql += "GROUP BY tabT.nucleus "
|
|
3502
|
+
# begin transaction
|
|
3503
|
+
self.conn.begin()
|
|
3504
|
+
# set cloud
|
|
3505
|
+
self.cur.execute(sql + comment, varMap)
|
|
3506
|
+
resList = self.cur.fetchall()
|
|
3507
|
+
# commit
|
|
3508
|
+
if not self._commit():
|
|
3509
|
+
raise RuntimeError("Commit error")
|
|
3510
|
+
# loop over all nuclei
|
|
3511
|
+
retMap = {}
|
|
3512
|
+
for nucleus, worldRW in resList:
|
|
3513
|
+
retMap[nucleus] = worldRW
|
|
3514
|
+
tmpLog.debug(f"RW={str(retMap)}")
|
|
3515
|
+
# return
|
|
3516
|
+
tmpLog.debug("done")
|
|
3517
|
+
return retMap
|
|
3518
|
+
except Exception:
|
|
3519
|
+
# roll back
|
|
3520
|
+
self._rollback()
|
|
3521
|
+
# error
|
|
3522
|
+
self.dump_error_message(tmpLog)
|
|
3523
|
+
return None
|
|
3524
|
+
|
|
3525
|
+
# calculate WORLD RW for tasks
|
|
3526
|
+
def calculateTaskWorldRW_JEDI(self, jediTaskID):
|
|
3527
|
+
comment = " /* JediDBProxy.calculateTaskWorldRW_JEDI */"
|
|
3528
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
3529
|
+
tmpLog.debug("start")
|
|
3530
|
+
try:
|
|
3531
|
+
# sql to get RW
|
|
3532
|
+
sql = (
|
|
3533
|
+
"SELECT (nEvents-nEventsUsed)*(CASE "
|
|
3534
|
+
"WHEN cpuTime IS NULL THEN 300 "
|
|
3535
|
+
"WHEN cpuTimeUnit='mHS06sPerEvent' OR cpuTimeUnit='mHS06sPerEventFixed' THEN cpuTime/1000 "
|
|
3536
|
+
"ELSE cpuTime END) "
|
|
3537
|
+
)
|
|
3538
|
+
sql += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_Datasets tabD ".format(panda_config.schemaJEDI)
|
|
3539
|
+
sql += "WHERE tabT.jediTaskID=tabD.jediTaskID AND masterID IS NULL "
|
|
3540
|
+
sql += "AND tabT.jediTaskID=:jediTaskID "
|
|
3541
|
+
varMap = {}
|
|
3542
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
3543
|
+
# begin transaction
|
|
3544
|
+
self.conn.begin()
|
|
3545
|
+
# get
|
|
3546
|
+
self.cur.execute(sql + comment, varMap)
|
|
3547
|
+
resRT = self.cur.fetchone()
|
|
3548
|
+
# commit
|
|
3549
|
+
if not self._commit():
|
|
3550
|
+
raise RuntimeError("Commit error")
|
|
3551
|
+
# locked by another
|
|
3552
|
+
if resRT is None:
|
|
3553
|
+
retVal = None
|
|
3554
|
+
else:
|
|
3555
|
+
retVal = resRT[0]
|
|
3556
|
+
tmpLog.debug(f"RW={retVal}")
|
|
3557
|
+
# return
|
|
3558
|
+
tmpLog.debug("done")
|
|
3559
|
+
return retVal
|
|
3560
|
+
except Exception:
|
|
3561
|
+
# roll back
|
|
3562
|
+
self._rollback()
|
|
3563
|
+
# error
|
|
3564
|
+
self.dump_error_message(tmpLog)
|
|
3565
|
+
return None
|
|
3566
|
+
|
|
3567
|
+
def load_sw_map(self):
|
|
3568
|
+
comment = " /* JediDBProxy.load_sw_map */"
|
|
3569
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
3570
|
+
tmp_log.debug("start")
|
|
3571
|
+
|
|
3572
|
+
sw_map = {}
|
|
3573
|
+
|
|
3574
|
+
try:
|
|
3575
|
+
# sql to get size
|
|
3576
|
+
sql = f"SELECT PANDA_QUEUE, DATA FROM {panda_config.schemaPANDA}.SW_TAGS"
|
|
3577
|
+
self.cur.execute(sql + comment)
|
|
3578
|
+
results = self.cur.fetchall()
|
|
3579
|
+
for panda_queue, data in results:
|
|
3580
|
+
sw_map[panda_queue] = json.loads(data)
|
|
3581
|
+
|
|
3582
|
+
tmp_log.debug("done")
|
|
3583
|
+
return sw_map
|
|
3584
|
+
|
|
3585
|
+
except Exception:
|
|
3586
|
+
self._rollback()
|
|
3587
|
+
self.dump_error_message(tmp_log)
|
|
3588
|
+
return None
|
|
3589
|
+
|
|
3590
|
+
def getNetworkMetrics(self, dst, keyList):
|
|
3591
|
+
"""
|
|
3592
|
+
Get the network metrics from a source to all possible destinations
|
|
3593
|
+
:param dst: destination site
|
|
3594
|
+
:param keyList: activity keys.
|
|
3595
|
+
:return: returns a dictionary with network values in the style
|
|
3596
|
+
{
|
|
3597
|
+
<dest>: {<key>: <value>, <key>: <value>},
|
|
3598
|
+
<dest>: {<key>: <value>, <key>: <value>},
|
|
3599
|
+
...
|
|
3600
|
+
}
|
|
3601
|
+
"""
|
|
3602
|
+
comment = " /* JediDBProxy.getNetworkMetrics */"
|
|
3603
|
+
tmpLog = self.create_tagged_logger(comment)
|
|
3604
|
+
tmpLog.debug("start")
|
|
3605
|
+
|
|
3606
|
+
latest_validity = naive_utcnow() - datetime.timedelta(minutes=60)
|
|
3607
|
+
|
|
3608
|
+
varMap = {":dst": dst, ":latest_validity": latest_validity}
|
|
3609
|
+
|
|
3610
|
+
key_var_names_str, key_var_map = get_sql_IN_bind_variables(keyList, prefix=":key")
|
|
3611
|
+
|
|
3612
|
+
sql = f"""
|
|
3613
|
+
SELECT src, key, value, ts FROM {panda_config.schemaJEDI}.network_matrix_kv
|
|
3614
|
+
WHERE dst = :dst AND key IN ({key_var_names_str})
|
|
3615
|
+
AND ts > :latest_validity
|
|
3616
|
+
"""
|
|
3617
|
+
|
|
3618
|
+
varMap.update(key_var_map)
|
|
3619
|
+
|
|
3620
|
+
self.cur.execute(sql + comment, varMap)
|
|
3621
|
+
resList = self.cur.fetchall()
|
|
3622
|
+
|
|
3623
|
+
networkMap = {}
|
|
3624
|
+
total = {}
|
|
3625
|
+
for res in resList:
|
|
3626
|
+
src, key, value, ts = res
|
|
3627
|
+
networkMap.setdefault(src, {})
|
|
3628
|
+
networkMap[src][key] = value
|
|
3629
|
+
total.setdefault(key, 0)
|
|
3630
|
+
try:
|
|
3631
|
+
total[key] += value
|
|
3632
|
+
except Exception:
|
|
3633
|
+
pass
|
|
3634
|
+
networkMap["total"] = total
|
|
3635
|
+
tmpLog.debug(f"network map to nucleus {dst} is: {networkMap}")
|
|
3636
|
+
|
|
3637
|
+
return networkMap
|
|
3638
|
+
|
|
3639
|
+
def getBackloggedNuclei(self):
|
|
3640
|
+
"""
|
|
3641
|
+
Return a list of nuclei, which has built up transfer backlog. We will consider a nucleus as backlogged,
|
|
3642
|
+
when it has over 2000 output transfers queued and there are more than 3 sites with queues over
|
|
3643
|
+
"""
|
|
3644
|
+
|
|
3645
|
+
comment = " /* JediDBProxy.getBackloggedNuclei */"
|
|
3646
|
+
tmpLog = self.create_tagged_logger(comment)
|
|
3647
|
+
tmpLog.debug("start")
|
|
3648
|
+
|
|
3649
|
+
latest_validity = naive_utcnow() - datetime.timedelta(minutes=60)
|
|
3650
|
+
|
|
3651
|
+
nqueued_cap = self.getConfigValue("taskbrokerage", "NQUEUED_NUC_CAP", "jedi")
|
|
3652
|
+
if nqueued_cap is None:
|
|
3653
|
+
nqueued_cap = 2000
|
|
3654
|
+
|
|
3655
|
+
varMap = {":latest_validity": latest_validity, ":nqueued_cap": nqueued_cap}
|
|
3656
|
+
|
|
3657
|
+
sql = f"""
|
|
3658
|
+
SELECT dst
|
|
3659
|
+
FROM {panda_config.schemaJEDI}.network_matrix_kv
|
|
3660
|
+
WHERE key = 'Production Output_queued'
|
|
3661
|
+
AND ts > :latest_validity
|
|
3662
|
+
GROUP BY dst
|
|
3663
|
+
HAVING SUM(value) > :nqueued_cap
|
|
3664
|
+
"""
|
|
3665
|
+
|
|
3666
|
+
self.cur.execute(sql + comment, varMap)
|
|
3667
|
+
try:
|
|
3668
|
+
backlogged_nuclei = [entry[0] for entry in self.cur.fetchall()]
|
|
3669
|
+
except IndexError:
|
|
3670
|
+
backlogged_nuclei = []
|
|
3671
|
+
|
|
3672
|
+
tmpLog.debug(f"Nuclei with a long backlog are: {backlogged_nuclei}")
|
|
3673
|
+
|
|
3674
|
+
return backlogged_nuclei
|
|
3675
|
+
|
|
3676
|
+
def getPandaSiteToOutputStorageSiteMapping(self):
|
|
3677
|
+
"""
|
|
3678
|
+
Get a mapping of panda sites to their storage site. We consider the storage site of the default ddm endpoint
|
|
3679
|
+
:return: dictionary with panda_site_name keys and site_name values
|
|
3680
|
+
"""
|
|
3681
|
+
comment = " /* JediDBProxy.getPandaSiteToOutputStorageSiteMapping */"
|
|
3682
|
+
tmpLog = self.create_tagged_logger(comment)
|
|
3683
|
+
tmpLog.debug("start")
|
|
3684
|
+
|
|
3685
|
+
sql = """
|
|
3686
|
+
SELECT pdr.panda_site_name, de.site_name, nvl(pdr.scope, 'default')
|
|
3687
|
+
FROM atlas_panda.panda_ddm_relation pdr, atlas_panda.ddm_endpoint de
|
|
3688
|
+
WHERE pdr.default_write = 'Y'
|
|
3689
|
+
AND pdr.ddm_endpoint_name = de.ddm_endpoint_name
|
|
3690
|
+
"""
|
|
3691
|
+
|
|
3692
|
+
self.cur.execute(sql + comment)
|
|
3693
|
+
resList = self.cur.fetchall()
|
|
3694
|
+
mapping = {}
|
|
3695
|
+
|
|
3696
|
+
for res in resList:
|
|
3697
|
+
pandaSiteName, siteName, scope = res
|
|
3698
|
+
mapping.setdefault(pandaSiteName, {})
|
|
3699
|
+
mapping[pandaSiteName][scope] = siteName
|
|
3700
|
+
|
|
3701
|
+
# tmpLog.debug('panda site to ATLAS site mapping is: {0}'.format(mapping))
|
|
3702
|
+
|
|
3703
|
+
tmpLog.debug("done")
|
|
3704
|
+
return mapping
|
|
3705
|
+
|
|
3706
|
+
def get_active_gshare_rtypes(self, vo):
|
|
3707
|
+
"""
|
|
3708
|
+
Gets the active gshare/resource wq combinations. Active means they have at least 1 job in (assigned, activate, starting, running, ...)
|
|
3709
|
+
:param vo: Virtual Organization
|
|
3710
|
+
"""
|
|
3711
|
+
comment = " /* DBProxy.get_active_gshare_rtypes */"
|
|
3712
|
+
tmp_log = self.create_tagged_logger(comment, f"vo={vo}")
|
|
3713
|
+
tmp_log.debug("start")
|
|
3714
|
+
|
|
3715
|
+
# define the var map of query parameters
|
|
3716
|
+
var_map = {":vo": vo}
|
|
3717
|
+
|
|
3718
|
+
# sql to query on pre-cached job statistics tables, creating a single result set with active gshares and resource workqueues
|
|
3719
|
+
sql_get_active_combinations = f"""
|
|
3720
|
+
WITH gshare_results AS (
|
|
3721
|
+
SELECT /*+ RESULT_CACHE */ gshare AS name, resource_type
|
|
3722
|
+
FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS
|
|
3723
|
+
WHERE vo=:vo
|
|
3724
|
+
UNION
|
|
3725
|
+
SELECT /*+ RESULT_CACHE */ gshare AS name, resource_type
|
|
3726
|
+
FROM {panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS
|
|
3727
|
+
WHERE vo=:vo
|
|
3728
|
+
), wq_results AS (
|
|
3729
|
+
SELECT jwq.QUEUE_NAME AS name, jss.resource_type
|
|
3730
|
+
FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS jss
|
|
3731
|
+
JOIN {panda_config.schemaPANDA}.JEDI_WORK_QUEUE jwq ON jss.WORKQUEUE_ID = jwq.QUEUE_ID
|
|
3732
|
+
WHERE jwq.QUEUE_FUNCTION = 'Resource' AND jss.vo=:vo AND jwq.vo=:vo
|
|
3733
|
+
UNION
|
|
3734
|
+
SELECT jwq.QUEUE_NAME AS name, jss.resource_type
|
|
3735
|
+
FROM {panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS jss
|
|
3736
|
+
JOIN {panda_config.schemaPANDA}.JEDI_WORK_QUEUE jwq ON jss.WORKQUEUE_ID = jwq.QUEUE_ID
|
|
3737
|
+
WHERE jwq.QUEUE_FUNCTION = 'Resource' AND jss.vo=:vo AND jwq.vo=:vo
|
|
3738
|
+
)
|
|
3739
|
+
SELECT name, resource_type FROM gshare_results
|
|
3740
|
+
UNION
|
|
3741
|
+
SELECT name, resource_type FROM wq_results
|
|
3742
|
+
GROUP BY name, resource_type
|
|
3743
|
+
"""
|
|
3744
|
+
|
|
3745
|
+
return_map = {}
|
|
3746
|
+
try:
|
|
3747
|
+
self.cur.arraysize = 1000
|
|
3748
|
+
self.cur.execute(f"{sql_get_active_combinations} {comment}", var_map)
|
|
3749
|
+
res = self.cur.fetchall()
|
|
3750
|
+
|
|
3751
|
+
# create map
|
|
3752
|
+
for name, resource_type in res:
|
|
3753
|
+
return_map.setdefault(name, [])
|
|
3754
|
+
return_map[name].append(resource_type)
|
|
3755
|
+
|
|
3756
|
+
tmp_log.debug("done")
|
|
3757
|
+
return return_map
|
|
3758
|
+
except Exception:
|
|
3759
|
+
self.dump_error_message(tmp_log)
|
|
3760
|
+
return {}
|
|
3761
|
+
|
|
3762
|
+
# get work queue map
|
|
3763
|
+
def getWorkQueueMap(self):
|
|
3764
|
+
self.refreshWorkQueueMap()
|
|
3765
|
+
return self.workQueueMap
|
|
3766
|
+
|
|
3767
|
+
# refresh work queue map
|
|
3768
|
+
def refreshWorkQueueMap(self):
|
|
3769
|
+
# avoid frequent lookup
|
|
3770
|
+
if self.updateTimeForWorkQueue is not None and (naive_utcnow() - self.updateTimeForWorkQueue) < datetime.timedelta(minutes=10):
|
|
3771
|
+
return
|
|
3772
|
+
comment = " /* JediDBProxy.refreshWorkQueueMap */"
|
|
3773
|
+
tmpLog = self.create_tagged_logger(comment)
|
|
3774
|
+
|
|
3775
|
+
leave_shares = self.get_sorted_leaves()
|
|
3776
|
+
|
|
3777
|
+
if self.workQueueMap is None:
|
|
3778
|
+
self.workQueueMap = WorkQueueMapper()
|
|
3779
|
+
# SQL
|
|
3780
|
+
sql = self.workQueueMap.getSqlQuery()
|
|
3781
|
+
try:
|
|
3782
|
+
# start transaction
|
|
3783
|
+
self.conn.begin()
|
|
3784
|
+
self.cur.arraysize = 1000
|
|
3785
|
+
self.cur.execute(sql + comment)
|
|
3786
|
+
res = self.cur.fetchall()
|
|
3787
|
+
if not self._commit():
|
|
3788
|
+
raise RuntimeError("Commit error")
|
|
3789
|
+
# make map
|
|
3790
|
+
self.workQueueMap.makeMap(res, leave_shares)
|
|
3791
|
+
tmpLog.debug("done")
|
|
3792
|
+
self.updateTimeForWorkQueue = naive_utcnow()
|
|
3793
|
+
return True
|
|
3794
|
+
except Exception:
|
|
3795
|
+
# roll back
|
|
3796
|
+
self._rollback()
|
|
3797
|
+
# error
|
|
3798
|
+
self.dump_error_message(tmpLog)
|
|
3799
|
+
return False
|
|
3800
|
+
|
|
3801
|
+
|
|
3802
|
+
# get entity module
|
|
3803
|
+
def get_entity_module(base_mod) -> EntityModule:
|
|
3804
|
+
return base_mod.get_composite_module("entity")
|