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,841 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
import socket
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
11
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
12
|
+
|
|
13
|
+
from pandaserver.config import panda_config
|
|
14
|
+
from pandaserver.taskbuffer.JediTaskSpec import (
|
|
15
|
+
push_status_changes as task_push_status_changes,
|
|
16
|
+
)
|
|
17
|
+
from pandaserver.taskbuffer.JobSpec import (
|
|
18
|
+
push_status_changes as job_push_status_changes,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if panda_config.backend == "oracle":
|
|
22
|
+
import oracledb
|
|
23
|
+
|
|
24
|
+
varNUMBER = oracledb.NUMBER
|
|
25
|
+
else:
|
|
26
|
+
varNUMBER = int
|
|
27
|
+
|
|
28
|
+
# topics in SQL_QUEUE
|
|
29
|
+
SQL_QUEUE_TOPIC_async_dataset_update = "async_dataset_update"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Internal caching of a result. Use only for information with low update frequency and low memory footprint
|
|
33
|
+
def memoize(f):
|
|
34
|
+
memo = {}
|
|
35
|
+
kwd_mark = object()
|
|
36
|
+
|
|
37
|
+
def helper(self, *args, **kwargs):
|
|
38
|
+
now = datetime.datetime.now()
|
|
39
|
+
key = args + (kwd_mark,) + tuple(sorted(kwargs.items()))
|
|
40
|
+
if key not in memo or memo[key]["timestamp"] < now - datetime.timedelta(hours=1):
|
|
41
|
+
tmp_data = {"value": f(self, *args, **kwargs), "timestamp": now}
|
|
42
|
+
memo[key] = tmp_data
|
|
43
|
+
return memo[key]["value"]
|
|
44
|
+
|
|
45
|
+
return helper
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# convert dict to bind variable dict
|
|
49
|
+
def convert_dict_to_bind_vars(item):
|
|
50
|
+
ret = dict()
|
|
51
|
+
for k in item:
|
|
52
|
+
ret[f":{k}"] = item[k]
|
|
53
|
+
return ret
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Base class for DB proxy modules
|
|
57
|
+
class BaseModule:
|
|
58
|
+
# constructor
|
|
59
|
+
def __init__(self, log_stream: LogWrapper):
|
|
60
|
+
self._log_stream = log_stream
|
|
61
|
+
self.conn = None
|
|
62
|
+
self.cur = None
|
|
63
|
+
self.mb_proxy_dict = None
|
|
64
|
+
self.useOtherError = False
|
|
65
|
+
self.backend = panda_config.backend
|
|
66
|
+
# retry count
|
|
67
|
+
self.nTry = 5
|
|
68
|
+
# hostname
|
|
69
|
+
self.myHostName = socket.getfqdn()
|
|
70
|
+
self.backend = panda_config.backend
|
|
71
|
+
# host name
|
|
72
|
+
self.hostname = None
|
|
73
|
+
# composite modules
|
|
74
|
+
self.composite_modules = {}
|
|
75
|
+
|
|
76
|
+
# typical input cache
|
|
77
|
+
self.typical_input_cache = {}
|
|
78
|
+
|
|
79
|
+
# list of work queues
|
|
80
|
+
self.workQueueMap = None
|
|
81
|
+
# update time for work queue map
|
|
82
|
+
self.updateTimeForWorkQueue = None
|
|
83
|
+
|
|
84
|
+
# mb proxy for JEDI
|
|
85
|
+
self.jedi_mb_proxy_dict = None
|
|
86
|
+
self.jedi_mb_proxy_dict_setter = None
|
|
87
|
+
|
|
88
|
+
# JEDI config
|
|
89
|
+
self.jedi_config = None
|
|
90
|
+
|
|
91
|
+
# set JEDI attributes
|
|
92
|
+
def set_jedi_attributes(self, jedi_config, jedi_mb_proxy_dict_setter):
|
|
93
|
+
self.jedi_config = jedi_config
|
|
94
|
+
self.jedi_mb_proxy_dict_setter = jedi_mb_proxy_dict_setter
|
|
95
|
+
|
|
96
|
+
# abstract method to commit
|
|
97
|
+
def connect(self, **kwargs):
|
|
98
|
+
"""
|
|
99
|
+
Commit the transaction
|
|
100
|
+
"""
|
|
101
|
+
raise NotImplementedError("connect is not implemented")
|
|
102
|
+
|
|
103
|
+
# commit
|
|
104
|
+
def _commit(self):
|
|
105
|
+
try:
|
|
106
|
+
self.conn.commit()
|
|
107
|
+
return True
|
|
108
|
+
except Exception:
|
|
109
|
+
self._log_stream.error("commit error")
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# rollback
|
|
113
|
+
def _rollback(self, useOtherError=False):
|
|
114
|
+
return_value = True
|
|
115
|
+
# rollback
|
|
116
|
+
err_code = None
|
|
117
|
+
self._log_stream.debug("rollback")
|
|
118
|
+
try:
|
|
119
|
+
self.conn.rollback()
|
|
120
|
+
except Exception:
|
|
121
|
+
self._log_stream.error("rollback error")
|
|
122
|
+
return_value = False
|
|
123
|
+
# reconnect if needed
|
|
124
|
+
try:
|
|
125
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
126
|
+
# get error code for postgres
|
|
127
|
+
if self.backend == "postgres":
|
|
128
|
+
try:
|
|
129
|
+
err_code = err_value.pgcode
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
# get ORA ErrorCode
|
|
133
|
+
if err_code is None:
|
|
134
|
+
err_code = str(err_value).split()[0]
|
|
135
|
+
err_code = err_code[:-1]
|
|
136
|
+
err_msg = f"rollback EC:{err_code} {err_value}"
|
|
137
|
+
self._log_stream.debug(err_msg)
|
|
138
|
+
# error codes for connection error
|
|
139
|
+
if self.backend == "oracle":
|
|
140
|
+
error_list_for_reconnect = [
|
|
141
|
+
"ORA-01012",
|
|
142
|
+
"ORA-01033",
|
|
143
|
+
"ORA-01034",
|
|
144
|
+
"ORA-01089",
|
|
145
|
+
"ORA-03113",
|
|
146
|
+
"ORA-03114",
|
|
147
|
+
"ORA-12203",
|
|
148
|
+
"ORA-12500",
|
|
149
|
+
"ORA-12571",
|
|
150
|
+
"ORA-03135",
|
|
151
|
+
"ORA-25402",
|
|
152
|
+
]
|
|
153
|
+
# other errors are apparently given when connection lost contact
|
|
154
|
+
if useOtherError:
|
|
155
|
+
error_list_for_reconnect += ["ORA-01861", "ORA-01008"]
|
|
156
|
+
elif self.backend == "postgres":
|
|
157
|
+
import psycopg2.errorcodes as psycopg_errorcodes
|
|
158
|
+
|
|
159
|
+
error_list_for_reconnect = [
|
|
160
|
+
psycopg_errorcodes.CONNECTION_EXCEPTION,
|
|
161
|
+
psycopg_errorcodes.SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
|
|
162
|
+
psycopg_errorcodes.CONNECTION_DOES_NOT_EXIST,
|
|
163
|
+
psycopg_errorcodes.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
|
|
164
|
+
psycopg_errorcodes.CONNECTION_FAILURE,
|
|
165
|
+
psycopg_errorcodes.READ_ONLY_SQL_TRANSACTION,
|
|
166
|
+
]
|
|
167
|
+
else:
|
|
168
|
+
# mysql error codes for connection error
|
|
169
|
+
import MySQLdb
|
|
170
|
+
from MySQLdb.constants.CR import (
|
|
171
|
+
CONN_HOST_ERROR,
|
|
172
|
+
CONNECTION_ERROR,
|
|
173
|
+
LOCALHOST_CONNECTION,
|
|
174
|
+
SERVER_LOST,
|
|
175
|
+
)
|
|
176
|
+
from MySQLdb.constants.ER import (
|
|
177
|
+
ACCESS_DENIED_ERROR,
|
|
178
|
+
DBACCESS_DENIED_ERROR,
|
|
179
|
+
ILLEGAL_VALUE_FOR_TYPE,
|
|
180
|
+
SERVER_SHUTDOWN,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
error_list_for_reconnect = [
|
|
184
|
+
ACCESS_DENIED_ERROR,
|
|
185
|
+
DBACCESS_DENIED_ERROR,
|
|
186
|
+
SERVER_SHUTDOWN,
|
|
187
|
+
CONNECTION_ERROR,
|
|
188
|
+
CONN_HOST_ERROR,
|
|
189
|
+
LOCALHOST_CONNECTION,
|
|
190
|
+
SERVER_LOST,
|
|
191
|
+
]
|
|
192
|
+
# other errors are apparently given when connection lost contact
|
|
193
|
+
if useOtherError:
|
|
194
|
+
error_list_for_reconnect += [ILLEGAL_VALUE_FOR_TYPE]
|
|
195
|
+
if err_code in error_list_for_reconnect:
|
|
196
|
+
# reconnect
|
|
197
|
+
reconnect_stat = self.connect(reconnect=True)
|
|
198
|
+
self._log_stream.debug(f"rollback reconnected {reconnect_stat}")
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
# return
|
|
202
|
+
return return_value
|
|
203
|
+
|
|
204
|
+
# add composite module
|
|
205
|
+
def add_composite_module(self, module_name, module):
|
|
206
|
+
self.composite_modules[module_name] = module
|
|
207
|
+
|
|
208
|
+
# get composite module
|
|
209
|
+
def get_composite_module(self, module_name):
|
|
210
|
+
return self.composite_modules.get(module_name, None)
|
|
211
|
+
|
|
212
|
+
# dump error message
|
|
213
|
+
def dump_error_message(self, tmp_log: LogWrapper):
|
|
214
|
+
"""
|
|
215
|
+
Dump error message to the log
|
|
216
|
+
|
|
217
|
+
:param tmp_log: log wrapper
|
|
218
|
+
"""
|
|
219
|
+
# error
|
|
220
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
221
|
+
err_str = f"{err_type.__name__} {err_value}"
|
|
222
|
+
err_str.strip()
|
|
223
|
+
err_str += " "
|
|
224
|
+
err_str += traceback.format_exc()
|
|
225
|
+
tmp_log.error(err_str)
|
|
226
|
+
|
|
227
|
+
# create logger with tag
|
|
228
|
+
def create_tagged_logger(self, comment: str, tag: str = None) -> LogWrapper:
|
|
229
|
+
"""
|
|
230
|
+
Create logger from function comment and tag
|
|
231
|
+
|
|
232
|
+
param comment: comment of the function
|
|
233
|
+
param tag: tag to add to the method name
|
|
234
|
+
return: logger
|
|
235
|
+
"""
|
|
236
|
+
method_name = comment.split(" ")[-2].split(".")[-1]
|
|
237
|
+
if tag is None:
|
|
238
|
+
tag = naive_utcnow().strftime("%Y-%m-%d/%H:%M:%S.%f")
|
|
239
|
+
method_name += f" < {tag} >"
|
|
240
|
+
tmp_log = LogWrapper(self._log_stream, method_name)
|
|
241
|
+
return tmp_log
|
|
242
|
+
|
|
243
|
+
# get configuration value. cached for an hour
|
|
244
|
+
@memoize
|
|
245
|
+
def getConfigValue(self, component, key, app="pandaserver", vo=None):
|
|
246
|
+
comment = " /* DBProxy.getConfigValue */"
|
|
247
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
248
|
+
varMap = {":component": component, ":key": key, ":app": app}
|
|
249
|
+
sql = """
|
|
250
|
+
SELECT value, value_json, type FROM ATLAS_PANDA.CONFIG
|
|
251
|
+
WHERE component=:component
|
|
252
|
+
AND key=:key
|
|
253
|
+
AND app=:app
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# If VO is specified, select only the config values for this VO or VO independent values
|
|
257
|
+
if vo:
|
|
258
|
+
varMap[":vo"] = vo
|
|
259
|
+
sql += "AND (vo=:vo or vo IS NULL)"
|
|
260
|
+
|
|
261
|
+
self.cur.execute(sql + comment, varMap)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
value_str, value_json_str, type = self.cur.fetchone()
|
|
265
|
+
except TypeError:
|
|
266
|
+
error_message = f"Specified key={key} not found for component={component} app={app}"
|
|
267
|
+
tmp_log.debug(error_message)
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
if type.lower() in ("str", "string"):
|
|
272
|
+
return value_str
|
|
273
|
+
elif type.lower() in ("int", "integer"):
|
|
274
|
+
return int(value_str)
|
|
275
|
+
elif type.lower() == "float":
|
|
276
|
+
return float(value_str)
|
|
277
|
+
elif type.lower() in ("bool", "boolean"):
|
|
278
|
+
if value_str.lower() == "true":
|
|
279
|
+
return True
|
|
280
|
+
else:
|
|
281
|
+
return False
|
|
282
|
+
elif type.lower() == "json":
|
|
283
|
+
return json.loads(value_json_str)
|
|
284
|
+
else:
|
|
285
|
+
raise ValueError
|
|
286
|
+
except json.decoder.JSONDecodeError:
|
|
287
|
+
tmp_log.debug(f"Could not decode. Value_json: {value_json_str}, Type: {type}")
|
|
288
|
+
return None
|
|
289
|
+
except ValueError:
|
|
290
|
+
tmp_log.debug(f"Wrong value/type pair. Value: {value_str}, Type: {type}")
|
|
291
|
+
return None
|
|
292
|
+
except Exception as e:
|
|
293
|
+
tmp_log.debug(f"Unexpected error: {str(e)}")
|
|
294
|
+
raise e
|
|
295
|
+
|
|
296
|
+
def getvalue_corrector(self, value):
|
|
297
|
+
"""
|
|
298
|
+
Needed to support old and new versions of cx_Oracle
|
|
299
|
+
:return:
|
|
300
|
+
"""
|
|
301
|
+
if isinstance(value, list): # cx_Oracle version >= 6.3
|
|
302
|
+
return value[0]
|
|
303
|
+
else: # cx_Oracle version < 6.3
|
|
304
|
+
return value
|
|
305
|
+
|
|
306
|
+
# get mb proxy
|
|
307
|
+
def get_mb_proxy(self, channel):
|
|
308
|
+
if self.mb_proxy_dict is None:
|
|
309
|
+
try:
|
|
310
|
+
if hasattr(panda_config, "mq_configFile") and panda_config.mq_configFile:
|
|
311
|
+
# delay import to open logger file inside python daemon
|
|
312
|
+
from pandaserver.taskbuffer.PanDAMsgProcessor import MsgProcAgent
|
|
313
|
+
|
|
314
|
+
out_q_list = [
|
|
315
|
+
"panda_jobstatus",
|
|
316
|
+
"panda_jedi",
|
|
317
|
+
"panda_pilot_topic",
|
|
318
|
+
"panda_pilot_queue",
|
|
319
|
+
]
|
|
320
|
+
mp_agent = MsgProcAgent(config_file=panda_config.mq_configFile)
|
|
321
|
+
mb_proxy_dict = mp_agent.start_passive_mode(in_q_list=[], out_q_list=out_q_list)
|
|
322
|
+
# stop with atexit
|
|
323
|
+
atexit.register(mp_agent.stop_passive_mode)
|
|
324
|
+
# return
|
|
325
|
+
self.mb_proxy_dict = mb_proxy_dict
|
|
326
|
+
except Exception:
|
|
327
|
+
comment = " /* DBProxy.get_mb_proxy */"
|
|
328
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
329
|
+
self.dump_error_message(tmp_log)
|
|
330
|
+
self.mb_proxy_dict = {}
|
|
331
|
+
if not self.mb_proxy_dict or channel not in self.mb_proxy_dict["out"]:
|
|
332
|
+
return None
|
|
333
|
+
return self.mb_proxy_dict["out"][channel]
|
|
334
|
+
|
|
335
|
+
# close connection
|
|
336
|
+
def close_connection(self):
|
|
337
|
+
if self.conn:
|
|
338
|
+
try:
|
|
339
|
+
self.conn.close()
|
|
340
|
+
except Exception:
|
|
341
|
+
pass
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# cleanup
|
|
345
|
+
def cleanup(self):
|
|
346
|
+
comment = " /* DBProxy.cleanup */"
|
|
347
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
348
|
+
tmp_log.debug("start")
|
|
349
|
+
self.close_connection()
|
|
350
|
+
atexit.unregister(self.close_connection)
|
|
351
|
+
tmp_log.debug("done")
|
|
352
|
+
|
|
353
|
+
# query an SQL
|
|
354
|
+
def querySQL(self, sql, arraySize=1000):
|
|
355
|
+
comment = " /* DBProxy.querySQL */"
|
|
356
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
357
|
+
try:
|
|
358
|
+
tmp_log.debug(f"SQL={sql} ")
|
|
359
|
+
# begin transaction
|
|
360
|
+
self.conn.begin()
|
|
361
|
+
self.cur.arraysize = arraySize
|
|
362
|
+
self.cur.execute(sql + comment)
|
|
363
|
+
res = self.cur.fetchall()
|
|
364
|
+
# commit
|
|
365
|
+
if not self._commit():
|
|
366
|
+
raise RuntimeError("Commit error")
|
|
367
|
+
return res
|
|
368
|
+
except Exception:
|
|
369
|
+
# roll back
|
|
370
|
+
self._rollback(self.useOtherError)
|
|
371
|
+
self.dump_error_message(tmp_log)
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
# query an SQL return Status
|
|
375
|
+
def querySQLS(self, sql, varMap, arraySize=1000):
|
|
376
|
+
comment = " /* DBProxy.querySQLS */"
|
|
377
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
378
|
+
try:
|
|
379
|
+
tmp_log.debug(f"SQL={sql} vapMap={varMap} ")
|
|
380
|
+
# begin transaction
|
|
381
|
+
self.conn.begin()
|
|
382
|
+
self.cur.arraysize = arraySize
|
|
383
|
+
ret = self.cur.execute(sql + comment, varMap)
|
|
384
|
+
if ret:
|
|
385
|
+
ret = True
|
|
386
|
+
if sql.startswith("INSERT") or sql.startswith("UPDATE") or sql.startswith("DELETE"):
|
|
387
|
+
res = self.cur.rowcount
|
|
388
|
+
else:
|
|
389
|
+
res = self.cur.fetchall()
|
|
390
|
+
# commit
|
|
391
|
+
if not self._commit():
|
|
392
|
+
raise RuntimeError("Commit error")
|
|
393
|
+
return ret, res
|
|
394
|
+
except Exception as e:
|
|
395
|
+
# roll back
|
|
396
|
+
self._rollback(self.useOtherError)
|
|
397
|
+
self.dump_error_message(tmp_log)
|
|
398
|
+
return -1, None
|
|
399
|
+
|
|
400
|
+
# execute an SQL return with executemany
|
|
401
|
+
def executemanySQL(self, sql, varMaps, arraySize=1000):
|
|
402
|
+
comment = " /* DBProxy.executemanySQL */"
|
|
403
|
+
try:
|
|
404
|
+
# begin transaction
|
|
405
|
+
self.conn.begin()
|
|
406
|
+
self.cur.arraysize = arraySize
|
|
407
|
+
ret = self.cur.executemany(sql + comment, varMaps)
|
|
408
|
+
if sql.startswith("INSERT") or sql.startswith("UPDATE") or sql.startswith("DELETE"):
|
|
409
|
+
res = self.cur.rowcount
|
|
410
|
+
else:
|
|
411
|
+
raise RuntimeError("Operation unsupported. Only INSERT, UPDATE, DELETE are allowed")
|
|
412
|
+
# commit
|
|
413
|
+
if not self._commit():
|
|
414
|
+
raise RuntimeError("Commit error")
|
|
415
|
+
return res
|
|
416
|
+
except Exception as e:
|
|
417
|
+
# roll back
|
|
418
|
+
self._rollback(self.useOtherError)
|
|
419
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
420
|
+
tmp_log.error(f"{sql} {str(varMaps)}")
|
|
421
|
+
self.dump_error_message(tmp_log)
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
# get CLOB
|
|
425
|
+
def getClobObj(self, sql, varMap, arraySize=10000, use_commit=True):
|
|
426
|
+
comment = " /* DBProxy.getClobObj */"
|
|
427
|
+
try:
|
|
428
|
+
# begin transaction
|
|
429
|
+
if use_commit:
|
|
430
|
+
self.conn.begin()
|
|
431
|
+
self.cur.arraysize = arraySize
|
|
432
|
+
ret = self.cur.execute(sql + comment, varMap)
|
|
433
|
+
if ret:
|
|
434
|
+
ret = True
|
|
435
|
+
res = []
|
|
436
|
+
for items in self.cur:
|
|
437
|
+
resItem = []
|
|
438
|
+
for item in items:
|
|
439
|
+
# read CLOB
|
|
440
|
+
try:
|
|
441
|
+
itemRead = item.read()
|
|
442
|
+
except AttributeError:
|
|
443
|
+
itemRead = item
|
|
444
|
+
resItem.append(itemRead)
|
|
445
|
+
# append
|
|
446
|
+
res.append(resItem)
|
|
447
|
+
# commit
|
|
448
|
+
if use_commit:
|
|
449
|
+
if not self._commit():
|
|
450
|
+
raise RuntimeError("Commit error")
|
|
451
|
+
return ret, res
|
|
452
|
+
except Exception as e:
|
|
453
|
+
# roll back
|
|
454
|
+
if use_commit:
|
|
455
|
+
self._rollback()
|
|
456
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
457
|
+
tmp_log.error(f"{sql} {str(varMap)}")
|
|
458
|
+
self.dump_error_message(tmp_log)
|
|
459
|
+
return -1, None
|
|
460
|
+
|
|
461
|
+
# wake up connection
|
|
462
|
+
def wakeUp(self):
|
|
463
|
+
comment = " /* DBProxy.wakeUp */"
|
|
464
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
465
|
+
for iTry in range(5):
|
|
466
|
+
try:
|
|
467
|
+
# check if the connection is working
|
|
468
|
+
self.conn.ping()
|
|
469
|
+
return
|
|
470
|
+
except Exception:
|
|
471
|
+
tmp_log.error(f"{iTry} : connection is dead")
|
|
472
|
+
self.dump_error_message(tmp_log)
|
|
473
|
+
# wait for reconnection
|
|
474
|
+
time.sleep(1)
|
|
475
|
+
self.connect(reconnect=True)
|
|
476
|
+
|
|
477
|
+
# transaction as a context manager
|
|
478
|
+
@contextmanager
|
|
479
|
+
def transaction(self, name: str | None = None, tmp_log=None):
|
|
480
|
+
"""
|
|
481
|
+
Context manager for transaction
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
name (str, optional): name of the transaction to be shown in the log
|
|
485
|
+
tmp_log (LogWrapper, optional): logger to use. If None, a new logger will be created
|
|
486
|
+
|
|
487
|
+
Yields:
|
|
488
|
+
Any: the cursor object for executing SQL commands
|
|
489
|
+
Any: the logger object for logging in DBProxy
|
|
490
|
+
"""
|
|
491
|
+
comment = " /* DBProxy.transaction */"
|
|
492
|
+
try:
|
|
493
|
+
if tmp_log is None:
|
|
494
|
+
tmp_log = self.create_tagged_logger(comment, tag=name)
|
|
495
|
+
tmp_log.debug("transaction start")
|
|
496
|
+
# begin transaction
|
|
497
|
+
self.conn.begin()
|
|
498
|
+
# cursor and logger for the with block
|
|
499
|
+
yield (self.cur, tmp_log)
|
|
500
|
+
# commit transaction
|
|
501
|
+
if not self._commit():
|
|
502
|
+
raise RuntimeError("commit error")
|
|
503
|
+
tmp_log.debug("transaction done")
|
|
504
|
+
except Exception as e:
|
|
505
|
+
# roll back
|
|
506
|
+
self._rollback()
|
|
507
|
+
self.dump_error_message(tmp_log)
|
|
508
|
+
raise e
|
|
509
|
+
|
|
510
|
+
# record status change
|
|
511
|
+
def recordStatusChange(self, pandaID, jobStatus, jobInfo=None, infoMap={}, useCommit=True, no_late_bulk_exec=True, extracted_sqls=None):
|
|
512
|
+
comment = " /* DBProxy.recordStatusChange */"
|
|
513
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
514
|
+
# check config
|
|
515
|
+
if not hasattr(panda_config, "record_statuschange") or panda_config.record_statuschange is not True:
|
|
516
|
+
return
|
|
517
|
+
# get job info
|
|
518
|
+
varMap = {}
|
|
519
|
+
varMap[":PandaID"] = pandaID
|
|
520
|
+
varMap[":jobStatus"] = jobStatus
|
|
521
|
+
varMap[":modificationHost"] = self.myHostName
|
|
522
|
+
if jobInfo is not None:
|
|
523
|
+
varMap[":computingSite"] = jobInfo.computingSite
|
|
524
|
+
varMap[":cloud"] = jobInfo.cloud
|
|
525
|
+
varMap[":prodSourceLabel"] = jobInfo.prodSourceLabel
|
|
526
|
+
elif infoMap is not None:
|
|
527
|
+
varMap[":computingSite"] = infoMap["computingSite"]
|
|
528
|
+
varMap[":cloud"] = infoMap["cloud"]
|
|
529
|
+
varMap[":prodSourceLabel"] = infoMap["prodSourceLabel"]
|
|
530
|
+
else:
|
|
531
|
+
# no info
|
|
532
|
+
return
|
|
533
|
+
# convert NULL to None
|
|
534
|
+
for tmpKey in varMap:
|
|
535
|
+
if varMap[tmpKey] == "NULL":
|
|
536
|
+
varMap[tmpKey] = None
|
|
537
|
+
# insert
|
|
538
|
+
sql = "INSERT INTO ATLAS_PANDA.jobs_StatusLog "
|
|
539
|
+
sql += "(PandaID,modificationTime,jobStatus,prodSourceLabel,cloud,computingSite,modificationHost,modiftime_extended) "
|
|
540
|
+
sql += "VALUES (:PandaID,CURRENT_DATE,:jobStatus,:prodSourceLabel,:cloud,:computingSite,:modificationHost,CURRENT_TIMESTAMP) "
|
|
541
|
+
try:
|
|
542
|
+
# start transaction
|
|
543
|
+
if no_late_bulk_exec:
|
|
544
|
+
if useCommit:
|
|
545
|
+
self.conn.begin()
|
|
546
|
+
self.cur.execute(sql + comment, varMap)
|
|
547
|
+
# commit
|
|
548
|
+
if useCommit:
|
|
549
|
+
if not self._commit():
|
|
550
|
+
raise RuntimeError("Commit error")
|
|
551
|
+
else:
|
|
552
|
+
extracted_sqls.setdefault("state_change", {"sql": sql + comment, "vars": []})
|
|
553
|
+
extracted_sqls["state_change"]["vars"].append(varMap)
|
|
554
|
+
except Exception:
|
|
555
|
+
# roll back
|
|
556
|
+
if useCommit and no_late_bulk_exec:
|
|
557
|
+
self._rollback()
|
|
558
|
+
self.dump_error_message(tmp_log)
|
|
559
|
+
if not useCommit:
|
|
560
|
+
raise RuntimeError("recordStatusChange failed")
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
def push_job_status_message(
|
|
564
|
+
self,
|
|
565
|
+
job_spec,
|
|
566
|
+
panda_id,
|
|
567
|
+
status,
|
|
568
|
+
jedi_task_id=None,
|
|
569
|
+
special_handling=None,
|
|
570
|
+
extra_data=None,
|
|
571
|
+
):
|
|
572
|
+
comment = " /* DBProxy.push_job_status_message */"
|
|
573
|
+
if not (hasattr(panda_config, "mq_configFile") and panda_config.mq_configFile):
|
|
574
|
+
# skip if not configured
|
|
575
|
+
return
|
|
576
|
+
to_push = False
|
|
577
|
+
if special_handling is not None:
|
|
578
|
+
to_push = job_push_status_changes(special_handling)
|
|
579
|
+
elif job_spec is not None:
|
|
580
|
+
to_push = job_spec.push_status_changes()
|
|
581
|
+
# only run if to push status change
|
|
582
|
+
if not to_push:
|
|
583
|
+
return
|
|
584
|
+
# skip statuses unnecessary to push
|
|
585
|
+
if status in ["sent", "holding", "merging"]:
|
|
586
|
+
return
|
|
587
|
+
# skip if no mb to push to
|
|
588
|
+
mb_proxy = self.get_mb_proxy("panda_jobstatus")
|
|
589
|
+
if not mb_proxy:
|
|
590
|
+
return
|
|
591
|
+
if to_push:
|
|
592
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
593
|
+
# push job status change
|
|
594
|
+
try:
|
|
595
|
+
now_time = naive_utcnow()
|
|
596
|
+
now_ts = int(now_time.timestamp())
|
|
597
|
+
# init
|
|
598
|
+
inputs = []
|
|
599
|
+
computingsite = None
|
|
600
|
+
error_tmp_dict = {}
|
|
601
|
+
# info from job spec
|
|
602
|
+
if job_spec is not None:
|
|
603
|
+
# task id
|
|
604
|
+
if jedi_task_id is None:
|
|
605
|
+
jedi_task_id = job_spec.jediTaskID
|
|
606
|
+
# inputs
|
|
607
|
+
if job_spec.Files is not None:
|
|
608
|
+
for file_spec in job_spec.Files:
|
|
609
|
+
if file_spec.type in ["input", "pseudo_input"]:
|
|
610
|
+
inputs.append(file_spec.lfn)
|
|
611
|
+
# computing site
|
|
612
|
+
if job_spec.computingSite is not None:
|
|
613
|
+
computingsite = job_spec.computingSite
|
|
614
|
+
# error codes and diags
|
|
615
|
+
error_tmp_dict["piloterrorcode"] = job_spec.pilotErrorCode
|
|
616
|
+
error_tmp_dict["exeerrorcode"] = job_spec.exeErrorCode
|
|
617
|
+
error_tmp_dict["superrorcode"] = job_spec.supErrorCode
|
|
618
|
+
error_tmp_dict["ddmerrorcode"] = job_spec.ddmErrorCode
|
|
619
|
+
error_tmp_dict["brokerageerrorcode"] = job_spec.brokerageErrorCode
|
|
620
|
+
error_tmp_dict["jobdispatchererrorcode"] = job_spec.jobDispatcherErrorCode
|
|
621
|
+
error_tmp_dict["taskbuffererrorcode"] = job_spec.taskBufferErrorCode
|
|
622
|
+
error_tmp_dict["piloterrordiag"] = job_spec.pilotErrorDiag
|
|
623
|
+
error_tmp_dict["exeerrordiag"] = job_spec.exeErrorDiag
|
|
624
|
+
error_tmp_dict["superrordiag"] = job_spec.supErrorDiag
|
|
625
|
+
error_tmp_dict["ddmerrordiag"] = job_spec.ddmErrorDiag
|
|
626
|
+
error_tmp_dict["brokerageerrordiag"] = job_spec.brokerageErrorDiag
|
|
627
|
+
error_tmp_dict["jobdispatchererrordiag"] = job_spec.jobDispatcherErrorDiag
|
|
628
|
+
error_tmp_dict["taskbuffererrordiag"] = job_spec.taskBufferErrorDiag
|
|
629
|
+
# message
|
|
630
|
+
orig_msg_dict = {
|
|
631
|
+
"msg_type": "job_status",
|
|
632
|
+
"jobid": panda_id,
|
|
633
|
+
"taskid": jedi_task_id,
|
|
634
|
+
"status": status,
|
|
635
|
+
"timestamp": now_ts,
|
|
636
|
+
}
|
|
637
|
+
update_msg_dict = {
|
|
638
|
+
"computingsite": computingsite,
|
|
639
|
+
"inputs": inputs if inputs else None,
|
|
640
|
+
}
|
|
641
|
+
update_msg_dict.update(error_tmp_dict)
|
|
642
|
+
msg_dict = update_msg_dict.copy()
|
|
643
|
+
if extra_data:
|
|
644
|
+
msg_dict.update(extra_data)
|
|
645
|
+
msg_dict.update(orig_msg_dict)
|
|
646
|
+
msg = json.dumps(msg_dict)
|
|
647
|
+
if mb_proxy.got_disconnected:
|
|
648
|
+
mb_proxy.restart()
|
|
649
|
+
mb_proxy.send(msg)
|
|
650
|
+
tmp_log.debug(f"sent message: {msg}")
|
|
651
|
+
except Exception:
|
|
652
|
+
self.dump_error_message(tmp_log)
|
|
653
|
+
|
|
654
|
+
def insert_to_query_pool(self, topic, panda_id, task_id, sql, var_map, exec_order):
|
|
655
|
+
comment = " /* DBProxy.insert_to_query_pool */"
|
|
656
|
+
sqlI = (
|
|
657
|
+
"INSERT INTO {}.SQL_QUEUE (topic,PandaID,jediTaskID,creationTime,data,execution_order) "
|
|
658
|
+
"VALUES(:topic,:PandaID,:taskID,:creationTime,:data,:execution_order) ".format(panda_config.schemaPANDA)
|
|
659
|
+
)
|
|
660
|
+
varMap = {
|
|
661
|
+
":topic": topic,
|
|
662
|
+
":PandaID": panda_id,
|
|
663
|
+
":taskID": task_id,
|
|
664
|
+
":creationTime": naive_utcnow(),
|
|
665
|
+
":execution_order": exec_order,
|
|
666
|
+
":data": json.dumps((sql, var_map)),
|
|
667
|
+
}
|
|
668
|
+
self.cur.execute(sqlI + comment, varMap)
|
|
669
|
+
|
|
670
|
+
# check if exception is from NOWAIT
|
|
671
|
+
def isNoWaitException(self, errValue):
|
|
672
|
+
# for oracle
|
|
673
|
+
ora_err_code = str(errValue).split()[0]
|
|
674
|
+
ora_err_code = ora_err_code[:-1]
|
|
675
|
+
if ora_err_code == "ORA-00054":
|
|
676
|
+
return True
|
|
677
|
+
# for postgres
|
|
678
|
+
if type(errValue).__name__ == "LockNotAvailable":
|
|
679
|
+
return True
|
|
680
|
+
return False
|
|
681
|
+
|
|
682
|
+
# set super status
|
|
683
|
+
def setSuperStatus_JEDI(self, jediTaskID, superStatus):
|
|
684
|
+
comment = " /* JediDBProxy.setSuperStatus_JEDI */"
|
|
685
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
686
|
+
retTasks = []
|
|
687
|
+
try:
|
|
688
|
+
# sql to set super status
|
|
689
|
+
sqlCT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks "
|
|
690
|
+
sqlCT += "SET superStatus=:superStatus "
|
|
691
|
+
sqlCT += "WHERE jediTaskID=:jediTaskID "
|
|
692
|
+
# set super status
|
|
693
|
+
varMap = {}
|
|
694
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
695
|
+
varMap[":superStatus"] = superStatus
|
|
696
|
+
self.cur.execute(sqlCT + comment, varMap)
|
|
697
|
+
return True
|
|
698
|
+
except Exception:
|
|
699
|
+
# error
|
|
700
|
+
self.dump_error_message(tmpLog)
|
|
701
|
+
return False
|
|
702
|
+
|
|
703
|
+
# set DEFT status
|
|
704
|
+
def setDeftStatus_JEDI(self, jediTaskID, taskStatus):
|
|
705
|
+
comment = " /* JediDBProxy.setDeftStatus_JEDI */"
|
|
706
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
|
|
707
|
+
try:
|
|
708
|
+
sqlD = f"UPDATE {panda_config.schemaDEFT}.T_TASK "
|
|
709
|
+
sqlD += "SET status=:status,timeStamp=CURRENT_DATE "
|
|
710
|
+
sqlD += "WHERE taskID=:jediTaskID "
|
|
711
|
+
varMap = {}
|
|
712
|
+
varMap[":status"] = taskStatus
|
|
713
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
714
|
+
tmpLog.debug(sqlD + comment + str(varMap))
|
|
715
|
+
self.cur.execute(sqlD + comment, varMap)
|
|
716
|
+
return True
|
|
717
|
+
except Exception:
|
|
718
|
+
# error
|
|
719
|
+
self.dump_error_message(tmpLog)
|
|
720
|
+
return False
|
|
721
|
+
|
|
722
|
+
# task status logging
|
|
723
|
+
def record_task_status_change(self, jedi_task_id):
|
|
724
|
+
comment = " /* JediDBProxy.record_task_status_change */"
|
|
725
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
726
|
+
tmpLog.debug("start")
|
|
727
|
+
varMap = dict()
|
|
728
|
+
varMap[":jediTaskID"] = jedi_task_id
|
|
729
|
+
varMap[":modificationHost"] = socket.getfqdn()
|
|
730
|
+
# sql
|
|
731
|
+
sqlNS = (
|
|
732
|
+
"INSERT INTO {0}.TASKS_STATUSLOG "
|
|
733
|
+
"(jediTaskID,modificationTime,status,modificationHost,attemptNr,reason) "
|
|
734
|
+
"SELECT jediTaskID,CURRENT_TIMESTAMP,status,:modificationHost,attemptNr,"
|
|
735
|
+
"SUBSTR(errorDialog,0,255) "
|
|
736
|
+
"FROM {0}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
|
|
737
|
+
).format(panda_config.schemaJEDI)
|
|
738
|
+
self.cur.execute(sqlNS + comment, varMap)
|
|
739
|
+
tmpLog.debug("done")
|
|
740
|
+
|
|
741
|
+
# push task status message
|
|
742
|
+
def push_task_status_message(self, task_spec, jedi_task_id, status, split_rule=None):
|
|
743
|
+
to_push = False
|
|
744
|
+
if task_spec is not None:
|
|
745
|
+
to_push = task_spec.push_status_changes()
|
|
746
|
+
elif split_rule is not None:
|
|
747
|
+
to_push = task_push_status_changes(split_rule)
|
|
748
|
+
# only run if to push status change
|
|
749
|
+
if not to_push:
|
|
750
|
+
return
|
|
751
|
+
# skip statuses unnecessary to push
|
|
752
|
+
# if status in ['pending']:
|
|
753
|
+
# return
|
|
754
|
+
comment = " /* JediDBProxy.push_task_status_message */"
|
|
755
|
+
tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
756
|
+
tmpLog.debug("start")
|
|
757
|
+
# send task status messages to mq
|
|
758
|
+
try:
|
|
759
|
+
now_time = naive_utcnow()
|
|
760
|
+
now_ts = int(now_time.timestamp())
|
|
761
|
+
msg_dict = {
|
|
762
|
+
"msg_type": "task_status",
|
|
763
|
+
"taskid": jedi_task_id,
|
|
764
|
+
"status": status,
|
|
765
|
+
"timestamp": now_ts,
|
|
766
|
+
}
|
|
767
|
+
msg = json.dumps(msg_dict)
|
|
768
|
+
if self.jedi_mb_proxy_dict is None:
|
|
769
|
+
self.jedi_mb_proxy_dict = self.jedi_mb_proxy_dict_setter()
|
|
770
|
+
if self.jedi_mb_proxy_dict is None:
|
|
771
|
+
tmpLog.debug("Failed to get mb_proxy of internal MQs. Skipped ")
|
|
772
|
+
return
|
|
773
|
+
try:
|
|
774
|
+
mb_proxy = self.jedi_mb_proxy_dict["out"]["jedi_jobtaskstatus"]
|
|
775
|
+
except KeyError as e:
|
|
776
|
+
tmpLog.warning(f"Skipped due to {e} ; jedi_mb_proxy_dict is {self.jedi_mb_proxy_dict}")
|
|
777
|
+
return
|
|
778
|
+
if mb_proxy.got_disconnected:
|
|
779
|
+
mb_proxy.restart()
|
|
780
|
+
mb_proxy.send(msg)
|
|
781
|
+
except Exception:
|
|
782
|
+
self.dump_error_message(tmpLog)
|
|
783
|
+
tmpLog.debug("done")
|
|
784
|
+
|
|
785
|
+
# push message to message processors which triggers functions of agents
|
|
786
|
+
def push_task_trigger_message(self, msg_type, jedi_task_id, data_dict=None, priority=None, task_spec=None):
|
|
787
|
+
comment = " /* JediDBProxy.push_task_trigger_message */"
|
|
788
|
+
tmpLog = self.create_tagged_logger(comment, f"msg_type={msg_type} jediTaskID={jedi_task_id}")
|
|
789
|
+
tmpLog.debug("start")
|
|
790
|
+
# send task status messages to mq
|
|
791
|
+
try:
|
|
792
|
+
now_time = naive_utcnow()
|
|
793
|
+
now_ts = int(now_time.timestamp())
|
|
794
|
+
# get mbproxy
|
|
795
|
+
msg_dict = {}
|
|
796
|
+
if data_dict:
|
|
797
|
+
msg_dict.update(data_dict)
|
|
798
|
+
msg_dict.update(
|
|
799
|
+
{
|
|
800
|
+
"msg_type": msg_type,
|
|
801
|
+
"taskid": jedi_task_id,
|
|
802
|
+
"timestamp": now_ts,
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
msg = json.dumps(msg_dict)
|
|
806
|
+
if self.jedi_mb_proxy_dict is None:
|
|
807
|
+
self.jedi_mb_proxy_dict = self.jedi_mb_proxy_dict_setter()
|
|
808
|
+
if self.jedi_mb_proxy_dict is None:
|
|
809
|
+
tmpLog.debug("Failed to get mb_proxy of internal MQs. Skipped ")
|
|
810
|
+
return
|
|
811
|
+
try:
|
|
812
|
+
mq_name = msg_type
|
|
813
|
+
mb_proxy = self.jedi_mb_proxy_dict["out"][mq_name]
|
|
814
|
+
except KeyError as e:
|
|
815
|
+
tmpLog.warning(f"Skipped due to {e} ; jedi_mb_proxy_dict is {self.jedi_mb_proxy_dict}")
|
|
816
|
+
return
|
|
817
|
+
if mb_proxy.got_disconnected:
|
|
818
|
+
mb_proxy.restart()
|
|
819
|
+
# message priority
|
|
820
|
+
msg_priority = None
|
|
821
|
+
if priority is not None:
|
|
822
|
+
msg_priority = priority
|
|
823
|
+
elif task_spec is not None:
|
|
824
|
+
try:
|
|
825
|
+
if task_spec.prodSourceLabel == "user":
|
|
826
|
+
if task_spec.gshare in ["User Analysis", "Express Analysis"]:
|
|
827
|
+
msg_priority = 2
|
|
828
|
+
else:
|
|
829
|
+
msg_priority = 1
|
|
830
|
+
except AttributeError:
|
|
831
|
+
pass
|
|
832
|
+
# send message
|
|
833
|
+
if msg_priority is not None:
|
|
834
|
+
mb_proxy.send(msg, priority=msg_priority)
|
|
835
|
+
else:
|
|
836
|
+
mb_proxy.send(msg)
|
|
837
|
+
except Exception:
|
|
838
|
+
self.dump_error_message(tmpLog)
|
|
839
|
+
return
|
|
840
|
+
tmpLog.debug("done")
|
|
841
|
+
return True
|