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,180 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
6
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
7
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
8
|
+
|
|
9
|
+
from pandaserver.api.v1.common import generate_response, request_validation
|
|
10
|
+
from pandaserver.srvcore.CoreUtils import clean_user_id
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import idds.common.constants
|
|
14
|
+
import idds.common.utils
|
|
15
|
+
from idds.client.client import Client as iDDS_Client
|
|
16
|
+
from idds.client.clientmanager import ClientManager as iDDS_ClientManager
|
|
17
|
+
except ImportError:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
_logger = PandaLogger().getLogger("api_idds")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# json decoder for idds constants
|
|
24
|
+
def decode_idds_enum(d):
|
|
25
|
+
if "__idds_const__" in d:
|
|
26
|
+
items = d["__idds_const__"].split(".")
|
|
27
|
+
obj = idds.common.constants
|
|
28
|
+
for item in items:
|
|
29
|
+
obj = getattr(obj, item)
|
|
30
|
+
return obj
|
|
31
|
+
else:
|
|
32
|
+
return d
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
36
|
+
def relay_idds_command(req, command_name: str, args: str = None, kwargs: str = None, manager: bool = False, json_outputs: bool = False):
|
|
37
|
+
tmp_log = LogWrapper(
|
|
38
|
+
_logger,
|
|
39
|
+
f"relay_idds_command-{naive_utcnow().isoformat('/')}",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
if "+" in command_name:
|
|
44
|
+
command_name, idds_host = command_name.split("+")
|
|
45
|
+
else:
|
|
46
|
+
idds_host = idds.common.utils.get_rest_host()
|
|
47
|
+
|
|
48
|
+
if manager:
|
|
49
|
+
c = iDDS_ClientManager(idds_host)
|
|
50
|
+
else:
|
|
51
|
+
c = iDDS_Client(idds_host)
|
|
52
|
+
|
|
53
|
+
if not hasattr(c, command_name):
|
|
54
|
+
tmp_str = f"{command_name} is not a command of iDDS {c.__class__.__name__}"
|
|
55
|
+
tmp_log.error(tmp_str)
|
|
56
|
+
return generate_response(False, tmp_str)
|
|
57
|
+
|
|
58
|
+
if args:
|
|
59
|
+
try:
|
|
60
|
+
args = idds.common.utils.json_loads(args)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
tmp_log.warning(f"failed to load args json with {str(e)}")
|
|
63
|
+
args = json.loads(args, object_hook=decode_idds_enum)
|
|
64
|
+
else:
|
|
65
|
+
args = []
|
|
66
|
+
|
|
67
|
+
if kwargs:
|
|
68
|
+
try:
|
|
69
|
+
kwargs = idds.common.utils.json_loads(kwargs)
|
|
70
|
+
except Exception as e:
|
|
71
|
+
tmp_log.warning(f"failed to load kwargs json with {str(e)}")
|
|
72
|
+
kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
|
|
73
|
+
else:
|
|
74
|
+
kwargs = {}
|
|
75
|
+
|
|
76
|
+
# json outputs
|
|
77
|
+
if json_outputs and manager:
|
|
78
|
+
c.setup_json_outputs()
|
|
79
|
+
|
|
80
|
+
# set original username
|
|
81
|
+
dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
|
|
82
|
+
if dn:
|
|
83
|
+
c.set_original_user(user_name=clean_user_id(dn))
|
|
84
|
+
|
|
85
|
+
tmp_log.debug(f"execute: class={c.__class__.__name__} com={command_name} host={idds_host} args={str(args)[:200]} kwargs={str(kwargs)[:200]}")
|
|
86
|
+
ret = getattr(c, command_name)(*args, **kwargs)
|
|
87
|
+
tmp_log.debug(f"ret: {str(ret)[:200]}")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
return generate_response(True, "", ret)
|
|
91
|
+
except Exception:
|
|
92
|
+
# TODO: I don't know how to handle this
|
|
93
|
+
return idds.common.utils.json_dumps((True, ret))
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
tmp_str = f"failed to execute command with {str(e)}"
|
|
97
|
+
tmp_log.error(f"{tmp_str} {traceback.format_exc()}")
|
|
98
|
+
return generate_response(False, tmp_str)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# relay iDDS workflow command with ownership check
|
|
102
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
103
|
+
def execute_idds_workflow_command(req, command_name: str, kwargs: str = None, json_outputs: bool = False):
|
|
104
|
+
tmp_log = LogWrapper(
|
|
105
|
+
_logger,
|
|
106
|
+
f"execute_idds_workflow_command-{naive_utcnow().isoformat('/')}",
|
|
107
|
+
)
|
|
108
|
+
try:
|
|
109
|
+
if kwargs:
|
|
110
|
+
try:
|
|
111
|
+
kwargs = idds.common.utils.json_loads(kwargs)
|
|
112
|
+
except Exception:
|
|
113
|
+
kwargs = json.loads(kwargs, object_hook=decode_idds_enum)
|
|
114
|
+
else:
|
|
115
|
+
kwargs = {}
|
|
116
|
+
|
|
117
|
+
if "+" in command_name:
|
|
118
|
+
command_name, idds_host = command_name.split("+")
|
|
119
|
+
else:
|
|
120
|
+
idds_host = idds.common.utils.get_rest_host()
|
|
121
|
+
|
|
122
|
+
# check permission
|
|
123
|
+
if command_name in ["get_status"]:
|
|
124
|
+
check_owner = False
|
|
125
|
+
elif command_name in ["abort", "suspend", "resume", "retry", "finish"]:
|
|
126
|
+
check_owner = True
|
|
127
|
+
else:
|
|
128
|
+
tmp_message = f"{command_name} is unsupported"
|
|
129
|
+
tmp_log.error(tmp_message)
|
|
130
|
+
return generate_response(False, tmp_message)
|
|
131
|
+
|
|
132
|
+
# check owner
|
|
133
|
+
c = iDDS_ClientManager(idds_host)
|
|
134
|
+
if json_outputs:
|
|
135
|
+
c.setup_json_outputs()
|
|
136
|
+
dn = req.subprocess_env.get("SSL_CLIENT_S_DN")
|
|
137
|
+
if check_owner:
|
|
138
|
+
# requester
|
|
139
|
+
if not dn:
|
|
140
|
+
tmp_message = "SSL_CLIENT_S_DN is missing in HTTP request"
|
|
141
|
+
tmp_log.error(tmp_message)
|
|
142
|
+
return generate_response(False, tmp_message)
|
|
143
|
+
requester = clean_user_id(dn)
|
|
144
|
+
|
|
145
|
+
# get request_id
|
|
146
|
+
request_id = kwargs.get("request_id")
|
|
147
|
+
if request_id is None:
|
|
148
|
+
tmp_message = "request_id is missing"
|
|
149
|
+
tmp_log.error(tmp_message)
|
|
150
|
+
return generate_response(False, tmp_message)
|
|
151
|
+
|
|
152
|
+
# get request
|
|
153
|
+
req = c.get_requests(request_id=request_id)
|
|
154
|
+
if not req:
|
|
155
|
+
tmp_message = f"request {request_id} is not found"
|
|
156
|
+
tmp_log.error(tmp_message)
|
|
157
|
+
return generate_response(False, tmp_message)
|
|
158
|
+
|
|
159
|
+
user_name = req[0].get("username")
|
|
160
|
+
if user_name and user_name != requester:
|
|
161
|
+
tmp_message = f"request {request_id} is not owned by {requester}"
|
|
162
|
+
tmp_log.error(tmp_message)
|
|
163
|
+
return generate_response(False, tmp_message)
|
|
164
|
+
|
|
165
|
+
# set original username
|
|
166
|
+
if dn:
|
|
167
|
+
c.set_original_user(user_name=clean_user_id(dn))
|
|
168
|
+
|
|
169
|
+
# execute command
|
|
170
|
+
tmp_log.debug(f"com={command_name} host={idds_host} kwargs={str(kwargs)}")
|
|
171
|
+
ret = getattr(c, command_name)(**kwargs)
|
|
172
|
+
tmp_log.debug(str(ret))
|
|
173
|
+
|
|
174
|
+
if isinstance(ret, dict) and "message" in ret:
|
|
175
|
+
return generate_response(True, ret["message"], ret["status"])
|
|
176
|
+
|
|
177
|
+
return generate_response(True, "", ret)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
tmp_log.error(f"failed with {str(e)} {traceback.format_exc()}")
|
|
180
|
+
return generate_response(False, f"server failed with {str(e)}")
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import traceback
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
6
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
7
|
+
|
|
8
|
+
from pandaserver.api.v1.common import (
|
|
9
|
+
TimedMethod,
|
|
10
|
+
extract_primary_production_working_group,
|
|
11
|
+
extract_production_working_groups,
|
|
12
|
+
generate_response,
|
|
13
|
+
get_dn,
|
|
14
|
+
get_email_address,
|
|
15
|
+
get_fqan,
|
|
16
|
+
has_production_role,
|
|
17
|
+
request_validation,
|
|
18
|
+
)
|
|
19
|
+
from pandaserver.jobdispatcher import Protocol
|
|
20
|
+
from pandaserver.srvcore.panda_request import PandaRequest
|
|
21
|
+
from pandaserver.taskbuffer import JobUtils
|
|
22
|
+
from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
|
|
23
|
+
|
|
24
|
+
_logger = PandaLogger().getLogger("api_job")
|
|
25
|
+
|
|
26
|
+
global_task_buffer = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def init_task_buffer(task_buffer: TaskBuffer) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Initialize the task buffer. This method needs to be called before any other method in this module.
|
|
32
|
+
"""
|
|
33
|
+
global global_task_buffer
|
|
34
|
+
global_task_buffer = task_buffer
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@request_validation(_logger, secure=True, request_method="GET")
|
|
38
|
+
def get_status(req: PandaRequest, job_ids: List[int], timeout: int = 60) -> Dict:
|
|
39
|
+
"""
|
|
40
|
+
Get status of a job.
|
|
41
|
+
|
|
42
|
+
Gets the status for a job and command to the pilot if any. Requires a secure connection.
|
|
43
|
+
|
|
44
|
+
API details:
|
|
45
|
+
HTTP Method: GET
|
|
46
|
+
Path: /v1/job/get_status
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
50
|
+
job_ids(list of int): list of PanDA job IDs.
|
|
51
|
+
timeout(int, optional): The timeout value. Defaults to 60.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list of tupes with (status, command). When unsuccessful, the message field contains the error message and data an error code.
|
|
55
|
+
"""
|
|
56
|
+
tmp_logger = LogWrapper(_logger, f"get_status job_ids={job_ids} timeout={timeout}")
|
|
57
|
+
tmp_logger.debug("Start")
|
|
58
|
+
|
|
59
|
+
# The task buffer method expect a comma separated list of job_ids
|
|
60
|
+
job_ids_str = ",".join([str(job_id) for job_id in job_ids])
|
|
61
|
+
timed_method = TimedMethod(global_task_buffer.checkJobStatus, timeout)
|
|
62
|
+
timed_method.run(job_ids_str)
|
|
63
|
+
|
|
64
|
+
# Time out
|
|
65
|
+
if timed_method.result == Protocol.TimeOutToken:
|
|
66
|
+
tmp_logger.debug("Timed out")
|
|
67
|
+
return generate_response(False, message="time out", data={"code": Protocol.SC_TimeOut})
|
|
68
|
+
|
|
69
|
+
# No result
|
|
70
|
+
if not isinstance(timed_method.result, list):
|
|
71
|
+
tmp_logger.debug(f"Failed")
|
|
72
|
+
return generate_response(False, message="failed", data={"code": Protocol.SC_Failed})
|
|
73
|
+
|
|
74
|
+
# Success
|
|
75
|
+
data = timed_method.result
|
|
76
|
+
tmp_logger.debug(f"Done with data={data}")
|
|
77
|
+
return generate_response(True, data=data)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@request_validation(_logger, secure=True)
|
|
81
|
+
def get_description(self, job_ids: List[int]) -> Dict:
|
|
82
|
+
"""
|
|
83
|
+
Get description of a job.
|
|
84
|
+
|
|
85
|
+
Gets the description of a job from the main/active schema. The description includes job attributes, job parameters and related file attributes. Requires a secure connection.
|
|
86
|
+
|
|
87
|
+
API details:
|
|
88
|
+
HTTP Method: GET
|
|
89
|
+
Path: /v1/job/get_description
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
93
|
+
job_ids (list of int): List of PanDA job IDs.
|
|
94
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list with job descriptions. When unsuccessful, the message field contains the error message and data an error code.
|
|
98
|
+
"""
|
|
99
|
+
tmp_logger = LogWrapper(_logger, f"get_description")
|
|
100
|
+
tmp_logger.debug("Start")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
tmp_logger.debug(f"Number of requested PanDA IDs: {len(job_ids)}")
|
|
104
|
+
max_ids = 5500
|
|
105
|
+
if len(job_ids) > max_ids:
|
|
106
|
+
tmp_logger.error(f"List of PanDA IDs is longer than {max_ids}. Truncating the list.")
|
|
107
|
+
job_ids = job_ids[:max_ids]
|
|
108
|
+
except Exception:
|
|
109
|
+
error_type, error_value, _ = sys.exc_info()
|
|
110
|
+
tmp_logger.error(f"Failed deserializing the list of PanDA IDs: {error_type} {error_value}")
|
|
111
|
+
job_ids = []
|
|
112
|
+
|
|
113
|
+
tmp_logger.debug(f"Retrieving data for {job_ids}")
|
|
114
|
+
ret = [job.to_dict_advanced() if job is not None else None for job in global_task_buffer.peekJobs(job_ids)]
|
|
115
|
+
_logger.debug("Done")
|
|
116
|
+
|
|
117
|
+
return generate_response(True, data=ret)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@request_validation(_logger, secure=True)
|
|
121
|
+
def get_description_incl_archive(req: PandaRequest, job_ids: List[int]) -> Dict:
|
|
122
|
+
"""
|
|
123
|
+
Get description of a job.
|
|
124
|
+
|
|
125
|
+
Gets the description of a job, also looking into the secondary/archive schema. The description includes job attributes, job parameters and related file attributes. Requires a secure connection.
|
|
126
|
+
|
|
127
|
+
API details:
|
|
128
|
+
HTTP Method: GET
|
|
129
|
+
Path: /v1/job/get_description_incl_archive
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
req(PandaRequest): internally generated request object containing the env variables.
|
|
133
|
+
job_ids (list of int): List of PanDA job IDs.
|
|
134
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains a list with job descriptions. When unsuccessful, the message field contains the error message and data an error code.
|
|
138
|
+
"""
|
|
139
|
+
tmp_logger = LogWrapper(_logger, f"get_description_including_archive")
|
|
140
|
+
tmp_logger.debug("Start")
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
tmp_logger.debug(f"Number of requested PanDA IDs: {len(job_ids)}")
|
|
144
|
+
max_ids = 5500
|
|
145
|
+
if len(job_ids) > max_ids:
|
|
146
|
+
tmp_logger.error(f"List of PanDA IDs is longer than {max_ids}. Truncating the list.")
|
|
147
|
+
job_ids = job_ids[:max_ids]
|
|
148
|
+
except Exception:
|
|
149
|
+
error_type, error_value, _ = sys.exc_info()
|
|
150
|
+
tmp_logger.error(f"Failed deserializing the list of PanDA IDs: {error_type} {error_value}")
|
|
151
|
+
job_ids = []
|
|
152
|
+
|
|
153
|
+
tmp_logger.debug(f"Retrieving data for {str(job_ids)}")
|
|
154
|
+
|
|
155
|
+
ret = [job.to_dict_advanced() if job is not None else None for job in global_task_buffer.getFullJobStatus(job_ids)]
|
|
156
|
+
|
|
157
|
+
tmp_logger.debug("getFullJobStatus end")
|
|
158
|
+
return generate_response(True, data=ret)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@request_validation(_logger, secure=False, request_method="GET")
|
|
162
|
+
def generate_offline_execution_script(req: PandaRequest, job_id: int, days: int = None) -> Dict:
|
|
163
|
+
"""
|
|
164
|
+
Get execution script for a job.
|
|
165
|
+
|
|
166
|
+
Gets the execution script for a job, including Rucio download of input, ALRB setup, downloading transformation script and running the script. Requires a secure connection.
|
|
167
|
+
|
|
168
|
+
API details:
|
|
169
|
+
HTTP Method: GET
|
|
170
|
+
Path: /v1/job/generate_offline_execution_script
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
174
|
+
job_id (int): PanDA job ID
|
|
175
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field will contain `{"script": script}`. When unsuccessful, the message field contains the error message and data an error code.
|
|
179
|
+
"""
|
|
180
|
+
tmp_logger = LogWrapper(_logger, f"generate_offline_execution_script job_id={job_id} days={days}")
|
|
181
|
+
tmp_logger.debug("Start")
|
|
182
|
+
script = global_task_buffer.getScriptOfflineRunning(job_id, days)
|
|
183
|
+
|
|
184
|
+
if script.startswith("ERROR"):
|
|
185
|
+
tmp_logger.debug(f"Failed to generate script: {script}")
|
|
186
|
+
return script
|
|
187
|
+
|
|
188
|
+
tmp_logger.debug("Done")
|
|
189
|
+
return script
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@request_validation(_logger, secure=True, request_method="GET")
|
|
193
|
+
def get_metadata_for_analysis_jobs(req: PandaRequest, task_id: int) -> Dict:
|
|
194
|
+
"""
|
|
195
|
+
Get metadata for analysis jobs
|
|
196
|
+
|
|
197
|
+
Gets the metadata from the metatable for analysis jobs in `finished` status. Requires a secure connection.
|
|
198
|
+
|
|
199
|
+
API details:
|
|
200
|
+
HTTP Method: GET
|
|
201
|
+
Path: /v1/job/get_metadata_for_analysis_jobs
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
205
|
+
task_id (int): JEDI task ID
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful, the data field contains the medata. When unsuccessful, the message field contains the error message and data an error code.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
tmp_logger = LogWrapper(_logger, f"get_metadata_for_analysis_jobs task_id={task_id}")
|
|
212
|
+
tmp_logger.debug("Start")
|
|
213
|
+
|
|
214
|
+
metadata = global_task_buffer.getUserJobMetadata(task_id)
|
|
215
|
+
if not metadata:
|
|
216
|
+
tmp_logger.debug("No metadata found")
|
|
217
|
+
return generate_response(True, message="No metadata found", data={})
|
|
218
|
+
|
|
219
|
+
tmp_logger.debug("Done")
|
|
220
|
+
return generate_response(True, data=metadata)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
224
|
+
def kill(req, job_ids: List[int], code: int = None, use_email_as_id: bool = False, kill_options: List[str] = []):
|
|
225
|
+
"""
|
|
226
|
+
Kill the jobs
|
|
227
|
+
|
|
228
|
+
Kills the jobs with the given PanDA IDs. Requires a secure connection.
|
|
229
|
+
|
|
230
|
+
API details:
|
|
231
|
+
HTTP Method: POST
|
|
232
|
+
Path: /v1/job/kill
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
236
|
+
job_ids (list): List of PanDA job IDs
|
|
237
|
+
code (int, optional): The kill code. Defaults to None.
|
|
238
|
+
```
|
|
239
|
+
code
|
|
240
|
+
2: expire
|
|
241
|
+
3: aborted
|
|
242
|
+
4: expire in waiting
|
|
243
|
+
7: retry by server
|
|
244
|
+
8: rebrokerage
|
|
245
|
+
9: force kill
|
|
246
|
+
10: fast rebrokerage in overloaded PQ
|
|
247
|
+
50: kill by JEDI
|
|
248
|
+
51: reassigned by JEDI
|
|
249
|
+
52: force kill by JEDI
|
|
250
|
+
55: killed since task is (almost) done
|
|
251
|
+
60: workload was terminated by the pilot without actual work
|
|
252
|
+
91: kill user jobs with prod role
|
|
253
|
+
99: force kill user jobs with prod role
|
|
254
|
+
```
|
|
255
|
+
use_email_as_id (bool, optional): Use the email as ID. Defaults to False.
|
|
256
|
+
kill_options (List, optional): Defaults to []. Possible options are: `keepUnmerged`, `jobSubStatus=xyz`
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. The data field contains a list of bools indicating the success of the kill operations.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
# retrieve the user information
|
|
263
|
+
user = get_dn(req)
|
|
264
|
+
is_production_manager = has_production_role(req)
|
|
265
|
+
fqans = get_fqan(req)
|
|
266
|
+
|
|
267
|
+
tmp_logger = LogWrapper(_logger, f"kill")
|
|
268
|
+
tmp_logger.debug(f"Start user: {user} code: {code} is_production_manager: {is_production_manager} fqans: {fqans} job_ids: {job_ids}")
|
|
269
|
+
|
|
270
|
+
# Get the user's email address if use_email_as_id is set
|
|
271
|
+
if use_email_as_id:
|
|
272
|
+
email = get_email_address(user)
|
|
273
|
+
if email:
|
|
274
|
+
user = email
|
|
275
|
+
|
|
276
|
+
# Extract working groups with production role from FQANs
|
|
277
|
+
wg_prod_roles = extract_production_working_groups(fqans)
|
|
278
|
+
|
|
279
|
+
# kill jobs
|
|
280
|
+
ret = global_task_buffer.killJobs(job_ids, user, code, is_production_manager, wg_prod_roles, kill_options)
|
|
281
|
+
tmp_logger.debug(f"Done with ret: {ret}")
|
|
282
|
+
return generate_response(True, data=ret)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
286
|
+
def reassign(req: PandaRequest, job_ids: List[int]):
|
|
287
|
+
"""
|
|
288
|
+
Reassign a list of jobs
|
|
289
|
+
|
|
290
|
+
Reassigns a list of jobs. Requires a secure connection.
|
|
291
|
+
|
|
292
|
+
API details:
|
|
293
|
+
HTTP Method: POST
|
|
294
|
+
Path: /v1/job/reassign
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
298
|
+
job_ids (list): List of PanDA job IDs
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
dict: The system response `{"success": True}`.
|
|
302
|
+
"""
|
|
303
|
+
# TODO: think about the default values for for_pending and first_submission. The resolve_true and resolve_false functions are confusing me.
|
|
304
|
+
|
|
305
|
+
tmp_logger = LogWrapper(_logger, f"reassign job_ids={job_ids}")
|
|
306
|
+
tmp_logger.debug("Start")
|
|
307
|
+
# taskBuffer.reassignJobs always returns True
|
|
308
|
+
global_task_buffer.reassignJobs(job_ids)
|
|
309
|
+
tmp_logger.debug(f"Done")
|
|
310
|
+
return generate_response(True)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
314
|
+
def set_command(req: PandaRequest, job_id: int, command: str):
|
|
315
|
+
"""
|
|
316
|
+
Set a pilot command
|
|
317
|
+
|
|
318
|
+
Sets a command to the pilot for a job. Requires a secure connection and production role.
|
|
319
|
+
|
|
320
|
+
API details:
|
|
321
|
+
HTTP Method: POST
|
|
322
|
+
Path: /v1/job/set_command
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
326
|
+
job_id (int): PanDA job ID
|
|
327
|
+
command (str): The command for the pilot, e.g. `tobekilled`
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
dict: The system response `{"success": success, "message": message}`.
|
|
331
|
+
"""
|
|
332
|
+
tmp_logger = LogWrapper(_logger, f"set_command job_id={job_id} command={command}")
|
|
333
|
+
tmp_logger.debug("Start")
|
|
334
|
+
success, message = global_task_buffer.send_command_to_job(job_id, command)
|
|
335
|
+
tmp_logger.debug("Done with success={success} message={message}")
|
|
336
|
+
return generate_response(success, message=message)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
340
|
+
def set_debug_mode(req: PandaRequest, job_id: int, mode: bool):
|
|
341
|
+
"""
|
|
342
|
+
Set the debug mode
|
|
343
|
+
|
|
344
|
+
Sets the debug mode for a job. Requires a secure connection and production role.
|
|
345
|
+
|
|
346
|
+
API details:
|
|
347
|
+
HTTP Method: POST
|
|
348
|
+
Path: /v1/job/set_debug_mode
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
352
|
+
job_id (int): PanDA job ID
|
|
353
|
+
mode (bool): True to set debug mode, False to unset debug mode
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
dict: The system response `{"success": success, "message": message}`.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
tmp_logger = LogWrapper(_logger, f"set_debug_mode job_id={job_id}")
|
|
360
|
+
|
|
361
|
+
user = get_dn(req)
|
|
362
|
+
is_production_manager = has_production_role(req)
|
|
363
|
+
fqans = get_fqan(req)
|
|
364
|
+
|
|
365
|
+
# get the primary working group with prod role
|
|
366
|
+
working_group = extract_primary_production_working_group(fqans)
|
|
367
|
+
|
|
368
|
+
tmp_logger.debug(f"Start user={user} mgr={is_production_manager} wg={working_group} fqans={str(fqans)}")
|
|
369
|
+
|
|
370
|
+
message = global_task_buffer.setDebugMode(user, job_id, is_production_manager, mode, working_group)
|
|
371
|
+
|
|
372
|
+
success = False
|
|
373
|
+
if message != "Succeeded":
|
|
374
|
+
success = True
|
|
375
|
+
|
|
376
|
+
return generate_response(success, message=message)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
380
|
+
def submit(req: PandaRequest, jobs: str):
|
|
381
|
+
"""
|
|
382
|
+
Submit jobs
|
|
383
|
+
|
|
384
|
+
Sets the debug mode for a job. Requires a secure connection.
|
|
385
|
+
|
|
386
|
+
API details:
|
|
387
|
+
HTTP Method: POST
|
|
388
|
+
Path: /v1/job/submit
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
392
|
+
jobs (str): JSON string with a list of job specs
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
dict: The system response `{"success": success, "message": message}`.
|
|
396
|
+
"""
|
|
397
|
+
tmp_logger = LogWrapper(_logger, f"submit")
|
|
398
|
+
user = get_dn(req)
|
|
399
|
+
fqans = get_fqan(req)
|
|
400
|
+
is_production_role = has_production_role(req)
|
|
401
|
+
host = req.get_remote_host()
|
|
402
|
+
|
|
403
|
+
# deserialize job specs
|
|
404
|
+
try:
|
|
405
|
+
jobs = JobUtils.load_jobs_json(jobs)
|
|
406
|
+
tmp_logger.debug(f"{user} len:{len(jobs)} is_production_role={is_production_role} FQAN:{fqans}")
|
|
407
|
+
max_jobs = 5000
|
|
408
|
+
if len(jobs) > max_jobs:
|
|
409
|
+
_logger.error(f"Number of jobs exceeded maximum {max_jobs}. Truncating the list.")
|
|
410
|
+
jobs = jobs[:max_jobs]
|
|
411
|
+
except Exception as ex:
|
|
412
|
+
error_message = f"Failed to deserialize jobs: {str(ex)} {traceback.format_exc()}"
|
|
413
|
+
tmp_logger.error(error_message)
|
|
414
|
+
return generate_response(False, message=error_message)
|
|
415
|
+
|
|
416
|
+
if not jobs:
|
|
417
|
+
return generate_response(False, message="No jobs were submitted")
|
|
418
|
+
|
|
419
|
+
# check prodSourceLabel and job_label are correct
|
|
420
|
+
for tmp_job in jobs:
|
|
421
|
+
# check production jobs are submitted with production role
|
|
422
|
+
if tmp_job.prodSourceLabel in ["managed"] and not is_production_role:
|
|
423
|
+
return generate_response(False, message=f"{user} is missing production for prodSourceLabel={tmp_job.prodSourceLabel} submission")
|
|
424
|
+
|
|
425
|
+
# validate the job_label
|
|
426
|
+
if tmp_job.job_label not in [None, "", "NULL"] and tmp_job.job_label not in JobUtils.job_labels:
|
|
427
|
+
return generate_response(False, message=f"job_label={tmp_job.job_label} is not valid")
|
|
428
|
+
|
|
429
|
+
sample_job = jobs[0]
|
|
430
|
+
|
|
431
|
+
# get user VO
|
|
432
|
+
try:
|
|
433
|
+
user_vo = "atlas"
|
|
434
|
+
if sample_job.VO not in [None, "", "NULL"]:
|
|
435
|
+
user_vo = sample_job.VO
|
|
436
|
+
except (IndexError, AttributeError) as e:
|
|
437
|
+
tmp_logger.error(f"User VO was not found and defaulted to {user_vo}. (Exception {e})")
|
|
438
|
+
|
|
439
|
+
# atlas jobs require FQANs
|
|
440
|
+
if user_vo == "atlas" and not fqans:
|
|
441
|
+
tmp_logger.error(f"Proxy was missing FQANs for {user_vo}")
|
|
442
|
+
|
|
443
|
+
# LSST: try to overwrite with pipeline username
|
|
444
|
+
if user_vo.lower() == "lsst":
|
|
445
|
+
try:
|
|
446
|
+
if sample_job.prodUserName and sample_job.prodUserName.lower() != "none":
|
|
447
|
+
user = sample_job.prodUserName
|
|
448
|
+
except AttributeError:
|
|
449
|
+
tmp_logger.error(f"VO {user_vo} check: username not found in job parameters and defaulted to submitter ({user})")
|
|
450
|
+
|
|
451
|
+
# store jobs
|
|
452
|
+
ret = global_task_buffer.storeJobs(jobs, user, fqans=fqans, hostname=host, userVO=user_vo)
|
|
453
|
+
tmp_logger.debug(f"{user} -> {len(ret)}")
|
|
454
|
+
|
|
455
|
+
# There was no response
|
|
456
|
+
if not ret:
|
|
457
|
+
return generate_response(False, message="Failed to submit jobs")
|
|
458
|
+
|
|
459
|
+
# There was a string for response, i.e. an error message
|
|
460
|
+
if isinstance(ret, str):
|
|
461
|
+
return generate_response(False, message=ret)
|
|
462
|
+
|
|
463
|
+
return generate_response(True, data=ret)
|