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,443 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
7
|
+
from pandaserver.taskbuffer import EventServiceUtils
|
|
8
|
+
|
|
9
|
+
# constants
|
|
10
|
+
TimeOutToken = "TimeOut"
|
|
11
|
+
|
|
12
|
+
# status codes
|
|
13
|
+
# succeeded
|
|
14
|
+
SC_Success = 0
|
|
15
|
+
# timeout
|
|
16
|
+
SC_TimeOut = 10
|
|
17
|
+
# no available jobs
|
|
18
|
+
SC_NoJobs = 20
|
|
19
|
+
# failed
|
|
20
|
+
SC_Failed = 30
|
|
21
|
+
# invalid token
|
|
22
|
+
SC_Invalid = 50
|
|
23
|
+
# invalid role
|
|
24
|
+
SC_Role = 60
|
|
25
|
+
# permission denied
|
|
26
|
+
SC_Perms = 70
|
|
27
|
+
# key missing
|
|
28
|
+
SC_MissKey = 80
|
|
29
|
+
# failure of proxy retrieval
|
|
30
|
+
SC_ProxyError = 90
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# response
|
|
34
|
+
class Response:
|
|
35
|
+
# constructor
|
|
36
|
+
def __init__(self, statusCode, errorDialog=None):
|
|
37
|
+
# create data object
|
|
38
|
+
self.data = {"StatusCode": statusCode}
|
|
39
|
+
if errorDialog is not None:
|
|
40
|
+
self.data["errorDialog"] = errorDialog
|
|
41
|
+
|
|
42
|
+
# URL encode
|
|
43
|
+
def encode(self, acceptJson=False):
|
|
44
|
+
if not acceptJson:
|
|
45
|
+
return urlencode(self.data)
|
|
46
|
+
else:
|
|
47
|
+
return {"type": "json", "content": json.dumps(self.data)}
|
|
48
|
+
|
|
49
|
+
# append Node
|
|
50
|
+
def appendNode(self, name, value):
|
|
51
|
+
self.data[name] = value
|
|
52
|
+
|
|
53
|
+
# append job
|
|
54
|
+
def appendJob(self, job, siteMapperCache=None):
|
|
55
|
+
# event service merge
|
|
56
|
+
if EventServiceUtils.isEventServiceMerge(job):
|
|
57
|
+
isEventServiceMerge = True
|
|
58
|
+
else:
|
|
59
|
+
isEventServiceMerge = False
|
|
60
|
+
# PandaID
|
|
61
|
+
self.data["PandaID"] = job.PandaID
|
|
62
|
+
# prodSourceLabel
|
|
63
|
+
self.data["prodSourceLabel"] = job.prodSourceLabel
|
|
64
|
+
# swRelease
|
|
65
|
+
self.data["swRelease"] = job.AtlasRelease
|
|
66
|
+
# homepackage
|
|
67
|
+
self.data["homepackage"] = job.homepackage
|
|
68
|
+
# transformation
|
|
69
|
+
self.data["transformation"] = job.transformation
|
|
70
|
+
# job name
|
|
71
|
+
self.data["jobName"] = job.jobName
|
|
72
|
+
# job definition ID
|
|
73
|
+
self.data["jobDefinitionID"] = job.jobDefinitionID
|
|
74
|
+
# cloud
|
|
75
|
+
self.data["cloud"] = job.cloud
|
|
76
|
+
# files
|
|
77
|
+
strIFiles = ""
|
|
78
|
+
strOFiles = ""
|
|
79
|
+
strDispatch = ""
|
|
80
|
+
strDisToken = ""
|
|
81
|
+
strDisTokenForOutput = ""
|
|
82
|
+
strDestination = ""
|
|
83
|
+
strRealDataset = ""
|
|
84
|
+
strRealDatasetIn = ""
|
|
85
|
+
strProdDBlock = ""
|
|
86
|
+
strDestToken = ""
|
|
87
|
+
strProdToken = ""
|
|
88
|
+
strProdTokenForOutput = ""
|
|
89
|
+
strGUID = ""
|
|
90
|
+
strFSize = ""
|
|
91
|
+
strCheckSum = ""
|
|
92
|
+
strFileDestinationSE = ""
|
|
93
|
+
strScopeIn = ""
|
|
94
|
+
strScopeOut = ""
|
|
95
|
+
strScopeLog = ""
|
|
96
|
+
logFile = ""
|
|
97
|
+
logGUID = ""
|
|
98
|
+
ddmEndPointIn = []
|
|
99
|
+
ddmEndPointOut = []
|
|
100
|
+
noOutput = []
|
|
101
|
+
siteSpec = None
|
|
102
|
+
inDsLfnMap = {}
|
|
103
|
+
inLFNset = set()
|
|
104
|
+
if siteMapperCache is not None:
|
|
105
|
+
siteMapper = siteMapperCache.get_object()
|
|
106
|
+
siteSpec = siteMapper.getSite(job.computingSite)
|
|
107
|
+
# resolve destSE
|
|
108
|
+
try:
|
|
109
|
+
job.destinationSE = siteMapper.resolveNucleus(job.destinationSE)
|
|
110
|
+
for tmpFile in job.Files:
|
|
111
|
+
tmpFile.destinationSE = siteMapper.resolveNucleus(tmpFile.destinationSE)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
siteMapperCache.release_object()
|
|
115
|
+
for file in job.Files:
|
|
116
|
+
if file.type == "input":
|
|
117
|
+
if file.lfn in inLFNset:
|
|
118
|
+
pass
|
|
119
|
+
else:
|
|
120
|
+
inLFNset.add(file.lfn)
|
|
121
|
+
if strIFiles != "":
|
|
122
|
+
strIFiles += ","
|
|
123
|
+
strIFiles += file.lfn
|
|
124
|
+
if strDispatch != "":
|
|
125
|
+
strDispatch += ","
|
|
126
|
+
strDispatch += file.dispatchDBlock
|
|
127
|
+
if strDisToken != "":
|
|
128
|
+
strDisToken += ","
|
|
129
|
+
strDisToken += file.dispatchDBlockToken
|
|
130
|
+
strProdDBlock += f"{file.prodDBlock},"
|
|
131
|
+
if not isEventServiceMerge:
|
|
132
|
+
strProdToken += f"{file.prodDBlockToken},"
|
|
133
|
+
else:
|
|
134
|
+
strProdToken += f"{job.metadata[1][file.lfn]},"
|
|
135
|
+
if strGUID != "":
|
|
136
|
+
strGUID += ","
|
|
137
|
+
strGUID += file.GUID
|
|
138
|
+
strRealDatasetIn += f"{file.dataset},"
|
|
139
|
+
strFSize += f"{file.fsize},"
|
|
140
|
+
if file.checksum not in ["", "NULL", None]:
|
|
141
|
+
strCheckSum += f"{file.checksum},"
|
|
142
|
+
else:
|
|
143
|
+
strCheckSum += f"{file.md5sum},"
|
|
144
|
+
strScopeIn += f"{file.scope},"
|
|
145
|
+
ddmEndPointIn.append(
|
|
146
|
+
self.getDdmEndpoint(
|
|
147
|
+
siteSpec,
|
|
148
|
+
file.dispatchDBlockToken,
|
|
149
|
+
"input",
|
|
150
|
+
job.prodSourceLabel,
|
|
151
|
+
job.job_label,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
if file.dataset not in inDsLfnMap:
|
|
155
|
+
inDsLfnMap[file.dataset] = []
|
|
156
|
+
inDsLfnMap[file.dataset].append(file.lfn)
|
|
157
|
+
if file.type == "output" or file.type == "log":
|
|
158
|
+
if strOFiles != "":
|
|
159
|
+
strOFiles += ","
|
|
160
|
+
strOFiles += file.lfn
|
|
161
|
+
if strDestination != "":
|
|
162
|
+
strDestination += ","
|
|
163
|
+
strDestination += file.destinationDBlock
|
|
164
|
+
if strRealDataset != "":
|
|
165
|
+
strRealDataset += ","
|
|
166
|
+
strRealDataset += file.dataset
|
|
167
|
+
strFileDestinationSE += f"{file.destinationSE},"
|
|
168
|
+
if file.type == "log":
|
|
169
|
+
logFile = file.lfn
|
|
170
|
+
logGUID = file.GUID
|
|
171
|
+
strScopeLog = file.scope
|
|
172
|
+
else:
|
|
173
|
+
strScopeOut += f"{file.scope},"
|
|
174
|
+
if strDestToken != "":
|
|
175
|
+
strDestToken += ","
|
|
176
|
+
strDestToken += re.sub("^ddd:", "dst:", file.destinationDBlockToken.split(",")[0])
|
|
177
|
+
strDisTokenForOutput += f"{file.dispatchDBlockToken},"
|
|
178
|
+
strProdTokenForOutput += f"{file.prodDBlockToken},"
|
|
179
|
+
ddmEndPointOut.append(
|
|
180
|
+
self.getDdmEndpoint(
|
|
181
|
+
siteSpec,
|
|
182
|
+
file.destinationDBlockToken.split(",")[0],
|
|
183
|
+
"output",
|
|
184
|
+
job.prodSourceLabel,
|
|
185
|
+
job.job_label,
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
if file.isAllowedNoOutput():
|
|
189
|
+
noOutput.append(file.lfn)
|
|
190
|
+
# inFiles
|
|
191
|
+
self.data["inFiles"] = strIFiles
|
|
192
|
+
# dispatch DBlock
|
|
193
|
+
self.data["dispatchDblock"] = strDispatch
|
|
194
|
+
# dispatch DBlock space token
|
|
195
|
+
self.data["dispatchDBlockToken"] = strDisToken
|
|
196
|
+
# dispatch DBlock space token for output
|
|
197
|
+
self.data["dispatchDBlockTokenForOut"] = strDisTokenForOutput[:-1]
|
|
198
|
+
# outFiles
|
|
199
|
+
self.data["outFiles"] = strOFiles
|
|
200
|
+
# destination DBlock
|
|
201
|
+
self.data["destinationDblock"] = strDestination
|
|
202
|
+
# destination DBlock space token
|
|
203
|
+
self.data["destinationDBlockToken"] = strDestToken
|
|
204
|
+
# prod DBlocks
|
|
205
|
+
self.data["prodDBlocks"] = strProdDBlock[:-1]
|
|
206
|
+
# prod DBlock space token
|
|
207
|
+
self.data["prodDBlockToken"] = strProdToken[:-1]
|
|
208
|
+
# real output datasets
|
|
209
|
+
self.data["realDatasets"] = strRealDataset
|
|
210
|
+
# real output datasets
|
|
211
|
+
self.data["realDatasetsIn"] = strRealDatasetIn[:-1]
|
|
212
|
+
# file's destinationSE
|
|
213
|
+
self.data["fileDestinationSE"] = strFileDestinationSE[:-1]
|
|
214
|
+
# log filename
|
|
215
|
+
self.data["logFile"] = logFile
|
|
216
|
+
# log GUID
|
|
217
|
+
self.data["logGUID"] = logGUID
|
|
218
|
+
# jobPars
|
|
219
|
+
self.data["jobPars"], ppSteps = job.extractMultiStepExec()
|
|
220
|
+
if ppSteps is not None:
|
|
221
|
+
self.data.update(ppSteps)
|
|
222
|
+
if job.to_encode_job_params():
|
|
223
|
+
self.data["jobPars"] = base64.b64encode(self.data["jobPars"].encode()).decode()
|
|
224
|
+
# attempt number
|
|
225
|
+
self.data["attemptNr"] = job.attemptNr
|
|
226
|
+
# GUIDs
|
|
227
|
+
self.data["GUID"] = strGUID
|
|
228
|
+
# checksum
|
|
229
|
+
self.data["checksum"] = strCheckSum[:-1]
|
|
230
|
+
# fsize
|
|
231
|
+
self.data["fsize"] = strFSize[:-1]
|
|
232
|
+
# scope
|
|
233
|
+
self.data["scopeIn"] = strScopeIn[:-1]
|
|
234
|
+
self.data["scopeOut"] = strScopeOut[:-1]
|
|
235
|
+
self.data["scopeLog"] = strScopeLog
|
|
236
|
+
# DDM endpoints
|
|
237
|
+
try:
|
|
238
|
+
self.data["ddmEndPointIn"] = ",".join(ddmEndPointIn)
|
|
239
|
+
except TypeError:
|
|
240
|
+
self.data["ddmEndPointIn"] = ""
|
|
241
|
+
try:
|
|
242
|
+
self.data["ddmEndPointOut"] = ",".join(ddmEndPointOut)
|
|
243
|
+
except TypeError:
|
|
244
|
+
self.data["ddmEndPointOut"] = ""
|
|
245
|
+
# destinationSE
|
|
246
|
+
self.data["destinationSE"] = job.destinationSE
|
|
247
|
+
# user ID
|
|
248
|
+
self.data["prodUserID"] = job.prodUserID
|
|
249
|
+
# CPU count
|
|
250
|
+
self.data["maxCpuCount"] = job.maxCpuCount
|
|
251
|
+
# RAM count
|
|
252
|
+
self.data["minRamCount"] = job.minRamCount
|
|
253
|
+
# disk count
|
|
254
|
+
self.data["maxDiskCount"] = job.maxDiskCount
|
|
255
|
+
# cmtconfig
|
|
256
|
+
if ppSteps is None or job.cmtConfig not in ["NULL", None]:
|
|
257
|
+
self.data["cmtConfig"] = job.cmtConfig
|
|
258
|
+
else:
|
|
259
|
+
self.data["cmtConfig"] = ""
|
|
260
|
+
# processingType
|
|
261
|
+
self.data["processingType"] = job.processingType
|
|
262
|
+
# transferType
|
|
263
|
+
self.data["transferType"] = job.transferType
|
|
264
|
+
# sourceSite
|
|
265
|
+
self.data["sourceSite"] = job.sourceSite
|
|
266
|
+
# current priority
|
|
267
|
+
self.data["currentPriority"] = job.currentPriority
|
|
268
|
+
# taskID
|
|
269
|
+
if job.lockedby == "jedi":
|
|
270
|
+
self.data["taskID"] = job.jediTaskID
|
|
271
|
+
else:
|
|
272
|
+
self.data["taskID"] = job.taskID
|
|
273
|
+
# core count
|
|
274
|
+
if job.coreCount in ["NULL", None]:
|
|
275
|
+
self.data["coreCount"] = 1
|
|
276
|
+
else:
|
|
277
|
+
self.data["coreCount"] = job.coreCount
|
|
278
|
+
# jobsetID
|
|
279
|
+
self.data["jobsetID"] = job.jobsetID
|
|
280
|
+
# requestID
|
|
281
|
+
self.data["reqID"] = job.reqID
|
|
282
|
+
# nucleus
|
|
283
|
+
self.data["nucleus"] = job.nucleus
|
|
284
|
+
# walltime
|
|
285
|
+
self.data["maxWalltime"] = job.maxWalltime
|
|
286
|
+
# resource type
|
|
287
|
+
self.data["resource_type"] = job.resource_type
|
|
288
|
+
# looping check
|
|
289
|
+
if job.is_no_looping_check():
|
|
290
|
+
self.data["loopingCheck"] = False
|
|
291
|
+
# debug mode
|
|
292
|
+
if job.is_debug_mode():
|
|
293
|
+
self.data["debug"] = "True"
|
|
294
|
+
# event service or job cloning or fine-grained
|
|
295
|
+
if EventServiceUtils.isJobCloningJob(job):
|
|
296
|
+
self.data["cloneJob"] = EventServiceUtils.getJobCloningType(job)
|
|
297
|
+
elif EventServiceUtils.isEventServiceJob(job) or EventServiceUtils.isJumboJob(job):
|
|
298
|
+
self.data["eventService"] = "True"
|
|
299
|
+
# prod DBlock space token for pre-merging output
|
|
300
|
+
self.data["prodDBlockTokenForOutput"] = strProdTokenForOutput[:-1]
|
|
301
|
+
elif EventServiceUtils.is_fine_grained_job(job):
|
|
302
|
+
self.data["eventService"] = "True"
|
|
303
|
+
# event service merge
|
|
304
|
+
if isEventServiceMerge:
|
|
305
|
+
self.data["eventServiceMerge"] = "True"
|
|
306
|
+
# write to file for ES merge
|
|
307
|
+
writeToFileStr = ""
|
|
308
|
+
try:
|
|
309
|
+
for outputName in job.metadata[0]:
|
|
310
|
+
inputList = job.metadata[0][outputName]
|
|
311
|
+
writeToFileStr += f"inputFor_{outputName}:"
|
|
312
|
+
for tmpInput in inputList:
|
|
313
|
+
writeToFileStr += f"{tmpInput},"
|
|
314
|
+
writeToFileStr = writeToFileStr[:-1]
|
|
315
|
+
writeToFileStr += "^"
|
|
316
|
+
writeToFileStr = writeToFileStr[:-1]
|
|
317
|
+
except Exception:
|
|
318
|
+
pass
|
|
319
|
+
self.data["writeToFile"] = writeToFileStr
|
|
320
|
+
elif job.writeInputToFile():
|
|
321
|
+
try:
|
|
322
|
+
# write input to file
|
|
323
|
+
writeToFileStr = ""
|
|
324
|
+
for inDS in inDsLfnMap:
|
|
325
|
+
inputList = inDsLfnMap[inDS]
|
|
326
|
+
inDS = re.sub("/$", "", inDS)
|
|
327
|
+
inDS = inDS.split(":")[-1]
|
|
328
|
+
writeToFileStr += f"tmpin_{inDS}:"
|
|
329
|
+
writeToFileStr += ",".join(inputList)
|
|
330
|
+
writeToFileStr += "^"
|
|
331
|
+
writeToFileStr = writeToFileStr[:-1]
|
|
332
|
+
self.data["writeToFile"] = writeToFileStr
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
# replace placeholder
|
|
336
|
+
if EventServiceUtils.isJumboJob(job) or EventServiceUtils.isCoJumboJob(job):
|
|
337
|
+
try:
|
|
338
|
+
for inDS in inDsLfnMap:
|
|
339
|
+
inputList = inDsLfnMap[inDS]
|
|
340
|
+
inDS = re.sub("/$", "", inDS)
|
|
341
|
+
inDS = inDS.split(":")[-1]
|
|
342
|
+
srcStr = f"tmpin__cnt_{inDS}"
|
|
343
|
+
dstStr = ",".join(inputList)
|
|
344
|
+
self.data["jobPars"] = self.data["jobPars"].replace(srcStr, dstStr)
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
# no output
|
|
348
|
+
if noOutput != []:
|
|
349
|
+
self.data["allowNoOutput"] = ",".join(noOutput)
|
|
350
|
+
# alternative stage-out
|
|
351
|
+
if job.getAltStgOut() is not None:
|
|
352
|
+
self.data["altStageOut"] = job.getAltStgOut()
|
|
353
|
+
# log to OS
|
|
354
|
+
if job.putLogToOS():
|
|
355
|
+
self.data["putLogToOS"] = "True"
|
|
356
|
+
# suppress execute string conversion
|
|
357
|
+
if job.noExecStrCnv():
|
|
358
|
+
self.data["noExecStrCnv"] = "True"
|
|
359
|
+
# in-file positional event number
|
|
360
|
+
if job.inFilePosEvtNum():
|
|
361
|
+
self.data["inFilePosEvtNum"] = "True"
|
|
362
|
+
# use prefetcher
|
|
363
|
+
if job.usePrefetcher():
|
|
364
|
+
self.data["usePrefetcher"] = "True"
|
|
365
|
+
# image name
|
|
366
|
+
if job.container_name not in ["NULL", None]:
|
|
367
|
+
self.data["container_name"] = job.container_name
|
|
368
|
+
# IO
|
|
369
|
+
self.data["ioIntensity"] = job.get_task_attribute("ioIntensity")
|
|
370
|
+
self.data["ioIntensityUnit"] = job.get_task_attribute("ioIntensityUnit")
|
|
371
|
+
# HPO
|
|
372
|
+
if job.is_hpo_workflow():
|
|
373
|
+
self.data["isHPO"] = "True"
|
|
374
|
+
# VP
|
|
375
|
+
if siteSpec is not None:
|
|
376
|
+
scope_input, scope_output = DataServiceUtils.select_scope(siteSpec, job.prodSourceLabel, job.job_label)
|
|
377
|
+
if siteSpec.use_vp(scope_input):
|
|
378
|
+
self.data["useVP"] = "True"
|
|
379
|
+
# on-site merging
|
|
380
|
+
if job.is_on_site_merging():
|
|
381
|
+
self.data["onSiteMerging"] = "True"
|
|
382
|
+
|
|
383
|
+
# set proxy key
|
|
384
|
+
def setProxyKey(self, proxyKey):
|
|
385
|
+
names = ["credname", "myproxy"]
|
|
386
|
+
for name in names:
|
|
387
|
+
if name in proxyKey:
|
|
388
|
+
self.data[name] = proxyKey[name]
|
|
389
|
+
else:
|
|
390
|
+
self.data[name] = ""
|
|
391
|
+
|
|
392
|
+
# set secret key for panda proxy
|
|
393
|
+
def setPandaProxySecretKey(self, secretKey):
|
|
394
|
+
self.data["pandaProxySecretKey"] = secretKey
|
|
395
|
+
|
|
396
|
+
# get ddm endpoint
|
|
397
|
+
def getDdmEndpoint(self, siteSpec, spaceToken, mode, prodSourceLabel, job_label):
|
|
398
|
+
scope_input, scope_output = DataServiceUtils.select_scope(siteSpec, prodSourceLabel, job_label)
|
|
399
|
+
if siteSpec is None or mode not in ["input", "output"]:
|
|
400
|
+
return ""
|
|
401
|
+
|
|
402
|
+
if mode == "input":
|
|
403
|
+
connected_endpoints = siteSpec.ddm_endpoints_input.get(scope_input)
|
|
404
|
+
elif mode == "output":
|
|
405
|
+
connected_endpoints = siteSpec.ddm_endpoints_output.get(scope_output)
|
|
406
|
+
|
|
407
|
+
endPoint = DataServiceUtils.getDestinationSE(spaceToken)
|
|
408
|
+
if endPoint and connected_endpoints and connected_endpoints.isAssociated(endPoint):
|
|
409
|
+
return endPoint
|
|
410
|
+
|
|
411
|
+
endPoint = DataServiceUtils.getDistributedDestination(spaceToken)
|
|
412
|
+
if endPoint and connected_endpoints and connected_endpoints.isAssociated(endPoint):
|
|
413
|
+
return endPoint
|
|
414
|
+
|
|
415
|
+
if mode == "input":
|
|
416
|
+
setokens = siteSpec.setokens_input.get(scope_input, [])
|
|
417
|
+
ddm = siteSpec.ddm_input.get(scope_input)
|
|
418
|
+
elif mode == "output":
|
|
419
|
+
setokens = siteSpec.setokens_output.get(scope_output, [])
|
|
420
|
+
ddm = siteSpec.ddm_output.get(scope_output)
|
|
421
|
+
if spaceToken in setokens:
|
|
422
|
+
return setokens[spaceToken]
|
|
423
|
+
|
|
424
|
+
# Protection against misconfigured sites
|
|
425
|
+
if not ddm:
|
|
426
|
+
ddm = ""
|
|
427
|
+
|
|
428
|
+
return ddm
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# check if secure connection
|
|
432
|
+
def isSecure(req):
|
|
433
|
+
if "SSL_CLIENT_S_DN" not in req.subprocess_env:
|
|
434
|
+
return False
|
|
435
|
+
return True
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# get user DN
|
|
439
|
+
def getUserDN(req):
|
|
440
|
+
try:
|
|
441
|
+
return req.subprocess_env["SSL_CLIENT_S_DN"]
|
|
442
|
+
except Exception:
|
|
443
|
+
return "None"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
watch job
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
12
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
13
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
14
|
+
|
|
15
|
+
from pandaserver.dataservice.closer import Closer
|
|
16
|
+
from pandaserver.jobdispatcher import ErrorCode
|
|
17
|
+
from pandaserver.taskbuffer import EventServiceUtils, retryModule
|
|
18
|
+
from pandaserver.taskbuffer.JobSpec import JobSpec
|
|
19
|
+
from pandaserver.taskbuffer.SupErrors import SupErrors
|
|
20
|
+
|
|
21
|
+
# logger
|
|
22
|
+
_logger = PandaLogger().getLogger("Watcher")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Watcher(threading.Thread):
|
|
26
|
+
# constructor
|
|
27
|
+
def __init__(self, taskBuffer, pandaID, single=False, sleepTime=360, sitemapper=None):
|
|
28
|
+
threading.Thread.__init__(self)
|
|
29
|
+
self.pandaID = pandaID
|
|
30
|
+
self.taskBuffer = taskBuffer
|
|
31
|
+
self.sleepTime = sleepTime
|
|
32
|
+
self.single = single
|
|
33
|
+
self.siteMapper = sitemapper
|
|
34
|
+
self.logger = LogWrapper(_logger, str(pandaID))
|
|
35
|
+
|
|
36
|
+
# main
|
|
37
|
+
def run(self):
|
|
38
|
+
try:
|
|
39
|
+
while True:
|
|
40
|
+
self.logger.debug("start")
|
|
41
|
+
# query job
|
|
42
|
+
job = self.taskBuffer.peekJobs(
|
|
43
|
+
[self.pandaID],
|
|
44
|
+
fromDefined=False,
|
|
45
|
+
fromArchived=False,
|
|
46
|
+
fromWaiting=False,
|
|
47
|
+
)[0]
|
|
48
|
+
# check job status
|
|
49
|
+
if job is None:
|
|
50
|
+
self.logger.debug("escape : not found")
|
|
51
|
+
return
|
|
52
|
+
self.logger.debug(f"in {job.jobStatus}")
|
|
53
|
+
if job.jobStatus not in [
|
|
54
|
+
"running",
|
|
55
|
+
"sent",
|
|
56
|
+
"starting",
|
|
57
|
+
"holding",
|
|
58
|
+
"stagein",
|
|
59
|
+
"stageout",
|
|
60
|
+
]:
|
|
61
|
+
if job.jobStatus == "transferring" and (job.prodSourceLabel in ["user", "panda"] or job.jobSubStatus not in [None, "NULL", ""]):
|
|
62
|
+
pass
|
|
63
|
+
else:
|
|
64
|
+
self.logger.debug(f"escape : wrong status {job.jobStatus}")
|
|
65
|
+
return
|
|
66
|
+
# time limit
|
|
67
|
+
timeLimit = naive_utcnow() - datetime.timedelta(minutes=self.sleepTime)
|
|
68
|
+
if job.modificationTime < timeLimit or (job.endTime != "NULL" and job.endTime < timeLimit):
|
|
69
|
+
self.logger.debug(f"{job.jobStatus} lastmod:{str(job.modificationTime)} endtime:{str(job.endTime)}")
|
|
70
|
+
destDBList = []
|
|
71
|
+
if job.jobStatus == "sent":
|
|
72
|
+
# sent job didn't receive reply from pilot within 30 min
|
|
73
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_SendError
|
|
74
|
+
job.jobDispatcherErrorDiag = "Sent job didn't receive reply from pilot within 30 min"
|
|
75
|
+
elif job.exeErrorDiag == "NULL" and job.pilotErrorDiag == "NULL":
|
|
76
|
+
# lost heartbeat
|
|
77
|
+
if job.jobDispatcherErrorDiag == "NULL":
|
|
78
|
+
if job.endTime == "NULL":
|
|
79
|
+
# normal lost heartbeat
|
|
80
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_Watcher
|
|
81
|
+
job.jobDispatcherErrorDiag = f"lost heartbeat : {str(job.modificationTime)}"
|
|
82
|
+
else:
|
|
83
|
+
if job.jobStatus == "holding":
|
|
84
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_Holding
|
|
85
|
+
elif job.jobStatus == "transferring":
|
|
86
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_Transferring
|
|
87
|
+
else:
|
|
88
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_Timeout
|
|
89
|
+
job.jobDispatcherErrorDiag = f"timeout in {job.jobStatus} : last heartbeat at {str(job.endTime)}"
|
|
90
|
+
# get worker
|
|
91
|
+
workerSpecs = self.taskBuffer.getWorkersForJob(job.PandaID)
|
|
92
|
+
if len(workerSpecs) > 0:
|
|
93
|
+
workerSpec = workerSpecs[0]
|
|
94
|
+
if workerSpec.status in [
|
|
95
|
+
"finished",
|
|
96
|
+
"failed",
|
|
97
|
+
"cancelled",
|
|
98
|
+
"missed",
|
|
99
|
+
]:
|
|
100
|
+
job.supErrorCode = SupErrors.error_codes["WORKER_ALREADY_DONE"]
|
|
101
|
+
job.supErrorDiag = f"worker already {workerSpec.status} at {str(workerSpec.endTime)} with {workerSpec.diagMessage}"
|
|
102
|
+
job.supErrorDiag = JobSpec.truncateStringAttr("supErrorDiag", job.supErrorDiag)
|
|
103
|
+
else:
|
|
104
|
+
# job recovery failed
|
|
105
|
+
job.jobDispatcherErrorCode = ErrorCode.EC_Recovery
|
|
106
|
+
job.jobDispatcherErrorDiag = f"job recovery failed for {self.sleepTime / 60} hours"
|
|
107
|
+
# set job status
|
|
108
|
+
job.jobStatus = "failed"
|
|
109
|
+
# set endTime for lost heartbeat
|
|
110
|
+
if job.endTime == "NULL":
|
|
111
|
+
# normal lost heartbeat
|
|
112
|
+
job.endTime = job.modificationTime
|
|
113
|
+
# set files status
|
|
114
|
+
for file in job.Files:
|
|
115
|
+
if file.type == "output" or file.type == "log":
|
|
116
|
+
file.status = "failed"
|
|
117
|
+
if file.destinationDBlock not in destDBList:
|
|
118
|
+
destDBList.append(file.destinationDBlock)
|
|
119
|
+
# event service
|
|
120
|
+
if EventServiceUtils.isEventServiceJob(job) and not EventServiceUtils.isJobCloningJob(job):
|
|
121
|
+
eventStat = self.taskBuffer.getEventStat(job.jediTaskID, job.PandaID)
|
|
122
|
+
# set sub status when no successful events
|
|
123
|
+
if EventServiceUtils.ST_finished not in eventStat:
|
|
124
|
+
job.jobSubStatus = "es_heartbeat"
|
|
125
|
+
# update job
|
|
126
|
+
self.taskBuffer.updateJobs([job], False)
|
|
127
|
+
# start closer
|
|
128
|
+
if job.jobStatus == "failed":
|
|
129
|
+
source = "jobDispatcherErrorCode"
|
|
130
|
+
error_code = job.jobDispatcherErrorCode
|
|
131
|
+
error_diag = job.jobDispatcherErrorDiag
|
|
132
|
+
errors = [
|
|
133
|
+
{
|
|
134
|
+
"source": source,
|
|
135
|
+
"error_code": error_code,
|
|
136
|
+
"error_diag": error_diag,
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
self.logger.debug("Watcher will call job_failure_postprocessing")
|
|
142
|
+
retryModule.job_failure_postprocessing(self.taskBuffer, job.PandaID, errors, job.attemptNr)
|
|
143
|
+
self.logger.debug("job_failure_postprocessing is back")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.logger.debug(f"job_failure_postprocessing excepted and needs to be investigated ({e}): {traceback.format_exc()}")
|
|
146
|
+
|
|
147
|
+
# updateJobs was successful and it failed a job with taskBufferErrorCode
|
|
148
|
+
try:
|
|
149
|
+
self.logger.debug("Watcher.run will peek the job")
|
|
150
|
+
job_tmp = self.taskBuffer.peekJobs(
|
|
151
|
+
[job.PandaID],
|
|
152
|
+
fromDefined=False,
|
|
153
|
+
fromArchived=True,
|
|
154
|
+
fromWaiting=False,
|
|
155
|
+
)[0]
|
|
156
|
+
if job_tmp.taskBufferErrorCode:
|
|
157
|
+
source = "taskBufferErrorCode"
|
|
158
|
+
error_code = job_tmp.taskBufferErrorCode
|
|
159
|
+
error_diag = job_tmp.taskBufferErrorDiag
|
|
160
|
+
self.logger.debug("Watcher.run 2 will call job_failure_postprocessing")
|
|
161
|
+
retryModule.job_failure_postprocessing(
|
|
162
|
+
self.taskBuffer,
|
|
163
|
+
job_tmp.PandaID,
|
|
164
|
+
source,
|
|
165
|
+
error_code,
|
|
166
|
+
error_diag,
|
|
167
|
+
job_tmp.attemptNr,
|
|
168
|
+
)
|
|
169
|
+
self.logger.debug("job_failure_postprocessing 2 is back")
|
|
170
|
+
except IndexError:
|
|
171
|
+
pass
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.logger.error(f"job_failure_postprocessing 2 excepted and needs to be investigated ({e}): {traceback.format_exc()}")
|
|
174
|
+
|
|
175
|
+
cThr = Closer(self.taskBuffer, destDBList, job)
|
|
176
|
+
cThr.run()
|
|
177
|
+
self.logger.debug("done")
|
|
178
|
+
return
|
|
179
|
+
# single action
|
|
180
|
+
if self.single:
|
|
181
|
+
return
|
|
182
|
+
# sleep
|
|
183
|
+
time.sleep(60 * self.sleepTime)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.logger.error(f"run() : {str(e)} {traceback.format_exc()}")
|
|
186
|
+
return
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import uvicorn
|
|
7
|
+
from fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
from pandaserver.api.v1.http_client import api_url
|
|
10
|
+
from pandaserver.config import mcp_config
|
|
11
|
+
from pandaserver.pandamcp.mcp_utils import create_tool
|
|
12
|
+
|
|
13
|
+
# remove or overwrite environment variables to avoid server's credential being used
|
|
14
|
+
for var in ["PANDA_AUTH_ID_TOKEN", "PANDA_AUTH_VO", "X509_USER_PROXY"]:
|
|
15
|
+
if var in os.environ:
|
|
16
|
+
del os.environ[var]
|
|
17
|
+
os.environ["X509_USER_PROXY"] = "/dev/null"
|
|
18
|
+
|
|
19
|
+
# extract API's module path
|
|
20
|
+
p = urlparse(api_url)
|
|
21
|
+
api_module_path = ".".join([seg for seg in p.path.strip("/").split("/") if seg])
|
|
22
|
+
|
|
23
|
+
# get a list of API endpoints to expose
|
|
24
|
+
endpoints_to_expose = {}
|
|
25
|
+
|
|
26
|
+
if os.path.exists(mcp_config.endpoint_list_file):
|
|
27
|
+
with open(mcp_config.endpoint_list_file, "r") as f:
|
|
28
|
+
endpoints_to_expose = json.load(f)
|
|
29
|
+
|
|
30
|
+
# add an example endpoint if the file does not exist
|
|
31
|
+
endpoints_to_expose.setdefault("system", [])
|
|
32
|
+
if "is_alive" not in endpoints_to_expose["system"]:
|
|
33
|
+
endpoints_to_expose["system"].append("is_alive")
|
|
34
|
+
|
|
35
|
+
# create FastMCP instance
|
|
36
|
+
main_mcp = FastMCP(name="Main")
|
|
37
|
+
|
|
38
|
+
# add tools
|
|
39
|
+
for mod, func_list in endpoints_to_expose.items():
|
|
40
|
+
api_module = import_module(f"pandaserver.{api_module_path}.{mod}_api")
|
|
41
|
+
for func_name in func_list:
|
|
42
|
+
func = getattr(api_module, func_name)
|
|
43
|
+
tool = create_tool(func)
|
|
44
|
+
main_mcp.add_tool(tool)
|
|
45
|
+
|
|
46
|
+
# get HTTP app
|
|
47
|
+
http_app = main_mcp.http_app(transport=mcp_config.transport)
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
uvicorn.run(
|
|
51
|
+
http_app,
|
|
52
|
+
host="0.0.0.0",
|
|
53
|
+
port=int(os.getenv("PANDA_SERVER_CONF_PORT_MCP", 25888)),
|
|
54
|
+
ssl_keyfile=mcp_config.ssl_keyfile,
|
|
55
|
+
ssl_certfile=mcp_config.ssl_certfile,
|
|
56
|
+
)
|