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,445 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
5
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
6
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
7
|
+
|
|
8
|
+
from pandaserver.api.v1.common import (
|
|
9
|
+
MESSAGE_DATABASE,
|
|
10
|
+
TIME_OUT,
|
|
11
|
+
TimedMethod,
|
|
12
|
+
generate_response,
|
|
13
|
+
get_dn,
|
|
14
|
+
request_validation,
|
|
15
|
+
)
|
|
16
|
+
from pandaserver.srvcore.panda_request import PandaRequest
|
|
17
|
+
from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
|
|
18
|
+
|
|
19
|
+
_logger = PandaLogger().getLogger("api_harvester")
|
|
20
|
+
|
|
21
|
+
global_task_buffer = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_task_buffer(task_buffer: TaskBuffer) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Initialize the task buffer. This method needs to be called before any other method in this module.
|
|
27
|
+
"""
|
|
28
|
+
global global_task_buffer
|
|
29
|
+
global_task_buffer = task_buffer
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
33
|
+
def update_workers(req: PandaRequest, harvester_id: str, workers: List) -> dict:
|
|
34
|
+
"""
|
|
35
|
+
Update workers.
|
|
36
|
+
|
|
37
|
+
Update the details for a list of workers. Requires a secure connection.
|
|
38
|
+
|
|
39
|
+
API details:
|
|
40
|
+
HTTP Method: POST
|
|
41
|
+
Path: /v1/harvester/update_workers
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
req(PandaRequest): internally generated request object
|
|
45
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
46
|
+
workers(list): list of worker dictionaries that describe the fields of a pandaserver/taskbuffer/WorkerSpec object.
|
|
47
|
+
```
|
|
48
|
+
[{"workerID": 1, "batchID": 1, "queueName": "queue1", "status": "running",
|
|
49
|
+
"computingSite": "site1", "nCore": 1, "nodeID": None,
|
|
50
|
+
"submitTime": "02-NOV-24 00:02:18", "startTime": "02-NOV-24 00:02:18", "endTime": None,
|
|
51
|
+
"jobType": "managed", "resourceType": "SCORE", "nativeExitCode": None, "nativeStatus": None,
|
|
52
|
+
"diagMessage": None, "nJobs": 1, "computingElement": "ce1", "syncLevel": 0,
|
|
53
|
+
"submissionHost": "submissionhost1", "harvesterHost": "harvesterhost1",
|
|
54
|
+
"errorCode": None, "minRamCount": 2000},...]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
tmp_logger = LogWrapper(_logger, f"update_workers harvester_id={harvester_id}")
|
|
62
|
+
tmp_logger.debug("Start")
|
|
63
|
+
success, message, data = False, "", None
|
|
64
|
+
time_start = naive_utcnow()
|
|
65
|
+
|
|
66
|
+
ret = global_task_buffer.updateWorkers(harvester_id, workers)
|
|
67
|
+
if not ret:
|
|
68
|
+
tmp_logger.error(f"Error updating database for workers: {workers}")
|
|
69
|
+
success, message = False, MESSAGE_DATABASE
|
|
70
|
+
else:
|
|
71
|
+
success, data = True, ret
|
|
72
|
+
|
|
73
|
+
time_delta = naive_utcnow() - time_start
|
|
74
|
+
tmp_logger.debug(f"Done. Took {time_delta.seconds}.{time_delta.microseconds // 1000:03d} sec")
|
|
75
|
+
|
|
76
|
+
return generate_response(success, message, data)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
80
|
+
def update_service_metrics(req: PandaRequest, harvester_id: str, metrics: list) -> Dict[str, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Update harvester service metrics.
|
|
83
|
+
|
|
84
|
+
Update the service metrics for a harvester instance. Requires a secure connection.
|
|
85
|
+
|
|
86
|
+
API details:
|
|
87
|
+
HTTP Method: POST
|
|
88
|
+
Path: /v1/harvester/update_service_metrics
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
req(PandaRequest): internally generated request object
|
|
92
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
93
|
+
metrics(list): list of triplets `[[host, timestamp, metric_dict],[host, timestamp, metric_dict]...]`. The metric dictionary is json encoded, as it is stored in the database like that.
|
|
94
|
+
```
|
|
95
|
+
harvester_host = "harvester_host.cern.ch"
|
|
96
|
+
creation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
97
|
+
metric = {
|
|
98
|
+
"rss_mib": 2737.36,
|
|
99
|
+
"memory_pc": 39.19,
|
|
100
|
+
"cpu_pc": 15.23,
|
|
101
|
+
"volume_data_pc": 20.0,
|
|
102
|
+
"cert_lifetime": {
|
|
103
|
+
"/data/atlpan/proxy/x509up_u25606_prod": 81,
|
|
104
|
+
"/data/atlpan/proxy/x509up_u25606_pilot": 81,
|
|
105
|
+
"/cephfs/atlpan/harvester/proxy/x509up_u25606_prod": 96,
|
|
106
|
+
"/cephfs/atlpan/harvester/proxy/x509up_u25606_pilot": 96,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# DBProxy expects the metrics in json format and stores them directly in the database
|
|
111
|
+
metrics = [[creation_time, harvester_host, json.dumps(metric)]]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
116
|
+
"""
|
|
117
|
+
tmp_logger = LogWrapper(_logger, f"update_service_metrics harvester_id={harvester_id}")
|
|
118
|
+
tmp_logger.debug("Start")
|
|
119
|
+
success, message, data = False, "", None
|
|
120
|
+
|
|
121
|
+
# update the metrics in the database
|
|
122
|
+
time_start = naive_utcnow()
|
|
123
|
+
|
|
124
|
+
ret = global_task_buffer.updateServiceMetrics(harvester_id, metrics)
|
|
125
|
+
if not ret:
|
|
126
|
+
tmp_logger.error(f"Error updating database for metrics: {metrics}")
|
|
127
|
+
success, message = False, MESSAGE_DATABASE
|
|
128
|
+
else:
|
|
129
|
+
success, data = True, ret
|
|
130
|
+
|
|
131
|
+
time_delta = naive_utcnow() - time_start
|
|
132
|
+
_logger.debug(f"Done. Took {time_delta.seconds}.{time_delta.microseconds // 1000:03d} sec")
|
|
133
|
+
|
|
134
|
+
return generate_response(success, message, data)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
138
|
+
def add_dialogs(req: PandaRequest, harvester_id: str, dialogs: list) -> Dict[str, Any]:
|
|
139
|
+
"""
|
|
140
|
+
Add harvester dialog messages.
|
|
141
|
+
|
|
142
|
+
Add messages for a harvester instance. Requires a secure connection.
|
|
143
|
+
|
|
144
|
+
API details:
|
|
145
|
+
HTTP Method: POST
|
|
146
|
+
Path: /v1/harvester/add_dialogs
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
req(PandaRequest): internally generated request object
|
|
150
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
151
|
+
dialogs(list): list of dialog dictionaries, e.g.
|
|
152
|
+
```
|
|
153
|
+
dialogs = [{
|
|
154
|
+
"diagID": 1,
|
|
155
|
+
"moduleName": "test_module",
|
|
156
|
+
"identifier": "test identifier",
|
|
157
|
+
"creationTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
|
|
158
|
+
"messageLevel": "INFO",
|
|
159
|
+
"diagMessage": "test message",
|
|
160
|
+
},...]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
165
|
+
"""
|
|
166
|
+
tmp_logger = LogWrapper(_logger, f"add_dialogs harvester_id={harvester_id}")
|
|
167
|
+
tmp_logger.debug("Start")
|
|
168
|
+
|
|
169
|
+
ret = global_task_buffer.addHarvesterDialogs(harvester_id, dialogs)
|
|
170
|
+
if not ret:
|
|
171
|
+
tmp_logger.error(f"Error updating database: {dialogs}")
|
|
172
|
+
return generate_response(False, message=MESSAGE_DATABASE)
|
|
173
|
+
|
|
174
|
+
tmp_logger.debug("Done")
|
|
175
|
+
return generate_response(True)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
179
|
+
def heartbeat(req: PandaRequest, harvester_id: str, data: dict = None) -> Dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Heartbeat for harvester.
|
|
182
|
+
|
|
183
|
+
Send a heartbeat for harvester and optionally update the instance data. User and host are retrieved from the request object and updated in the database. Requires a secure connection.
|
|
184
|
+
|
|
185
|
+
API details:
|
|
186
|
+
HTTP Method: POST
|
|
187
|
+
Path: /v1/harvester/heartbeat
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
req(PandaRequest): internally generated request object
|
|
191
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
192
|
+
data(dict): metadata dictionary to be updated in the PanDA database, e.g. `data = {"startTime": <start time>, "sw_version": <release version>, "commit_stamp": <commit timestamp>}`
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
197
|
+
"""
|
|
198
|
+
tmp_logger = LogWrapper(_logger, f"heartbeat harvester_id={harvester_id}")
|
|
199
|
+
tmp_logger.debug("Start")
|
|
200
|
+
|
|
201
|
+
# get user and hostname to record in harvester metadata
|
|
202
|
+
user = get_dn(req)
|
|
203
|
+
host = req.get_remote_host()
|
|
204
|
+
|
|
205
|
+
ret_message = global_task_buffer.harvesterIsAlive(user, host, harvester_id, data)
|
|
206
|
+
if not ret_message or ret_message != "succeeded":
|
|
207
|
+
tmp_logger.error(f"Error updating database: {data}")
|
|
208
|
+
return generate_response(False, message=MESSAGE_DATABASE)
|
|
209
|
+
|
|
210
|
+
tmp_logger.debug("Done")
|
|
211
|
+
return generate_response(True)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@request_validation(_logger, secure=True, request_method="GET")
|
|
215
|
+
def get_current_worker_id(req: PandaRequest, harvester_id: str) -> Dict[str, Any]:
|
|
216
|
+
"""
|
|
217
|
+
Get the current worker ID.
|
|
218
|
+
|
|
219
|
+
Retrieve the current worker ID. Requires a secure connection.
|
|
220
|
+
|
|
221
|
+
API details:
|
|
222
|
+
HTTP Method: GET
|
|
223
|
+
Path: /v1/harvester/get_current_worker_id
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
req(PandaRequest): internally generated request object
|
|
227
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
231
|
+
"""
|
|
232
|
+
tmp_logger = LogWrapper(_logger, "get_current_worker_id")
|
|
233
|
+
tmp_logger.debug("Start")
|
|
234
|
+
current_worker_id = global_task_buffer.get_max_worker_id(harvester_id)
|
|
235
|
+
tmp_logger.debug("Done")
|
|
236
|
+
|
|
237
|
+
if current_worker_id is None:
|
|
238
|
+
return generate_response(False, message=MESSAGE_DATABASE)
|
|
239
|
+
|
|
240
|
+
return generate_response(True, data=current_worker_id)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@request_validation(_logger, secure=True, request_method="GET")
|
|
244
|
+
def get_worker_statistics(req: PandaRequest) -> Dict[str, Any]:
|
|
245
|
+
"""
|
|
246
|
+
Get worker statistics.
|
|
247
|
+
|
|
248
|
+
Get statistics for all the workers managed across the Grid. Requires a secure connection.
|
|
249
|
+
|
|
250
|
+
API details:
|
|
251
|
+
HTTP Method: GET
|
|
252
|
+
Path: /v1/harvester/get_worker_statistics
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
req(PandaRequest): internally generated request object
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
259
|
+
"""
|
|
260
|
+
tmp_logger = LogWrapper(_logger, "get_worker_statistics")
|
|
261
|
+
tmp_logger.debug("Start")
|
|
262
|
+
worker_stats = global_task_buffer.getWorkerStats()
|
|
263
|
+
tmp_logger.debug("Done")
|
|
264
|
+
return generate_response(True, data=worker_stats)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
268
|
+
def report_worker_statistics(req: PandaRequest, harvester_id: str, panda_queue: str, statistics: str) -> Dict[str, Any]:
|
|
269
|
+
"""
|
|
270
|
+
Report worker statistics.
|
|
271
|
+
|
|
272
|
+
Report statistics for the workers managed by a harvester instance at a PanDA queue. Requires a secure connection.
|
|
273
|
+
|
|
274
|
+
API details:
|
|
275
|
+
HTTP Method: POST
|
|
276
|
+
Path: /v1/harvester/report_worker_statistics
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
req (PandaRequest): Internally generated request object.
|
|
280
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
281
|
+
panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
|
|
282
|
+
statistics(str): JSON string containing a dictionary with the statistics to be reported. It will be stored as a json in the database. E.g.
|
|
283
|
+
```
|
|
284
|
+
json.dumps({"user": {"SCORE": {"running": 1, "submitted": 1}}, "managed": {"MCORE": {"running": 1, "submitted": 1}}})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
289
|
+
"""
|
|
290
|
+
tmp_logger = LogWrapper(_logger, f"report_worker_statistics harvester_id={harvester_id}")
|
|
291
|
+
tmp_logger.debug("Start")
|
|
292
|
+
success, message = global_task_buffer.reportWorkerStats_jobtype(harvester_id, panda_queue, statistics)
|
|
293
|
+
tmp_logger.debug("Done")
|
|
294
|
+
return generate_response(success, message=message)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
298
|
+
def acquire_commands(req: PandaRequest, harvester_id: str, n_commands: int, timeout: int = 30) -> Dict[str, Any]:
|
|
299
|
+
"""
|
|
300
|
+
Get harvester commands.
|
|
301
|
+
|
|
302
|
+
Retrieves the commands for a specified harvester instance. Requires a secure connection and production role.
|
|
303
|
+
|
|
304
|
+
API details:
|
|
305
|
+
HTTP Method: POST
|
|
306
|
+
Path: /v1/harvester/acquire_commands
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
req(PandaRequest): The request object containing the environment variables.
|
|
310
|
+
harvester_id(str): harvester id, e.g. `harvester_central_A`
|
|
311
|
+
n_commands(int): The number of commands to retrieve, e.g. `10`.
|
|
312
|
+
timeout(int, optional): The timeout value. Defaults to `30`.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
316
|
+
"""
|
|
317
|
+
tmp_logger = LogWrapper(_logger, "acquire_commands")
|
|
318
|
+
tmp_logger.debug("Start")
|
|
319
|
+
|
|
320
|
+
timed_method = TimedMethod(global_task_buffer.getCommands, timeout)
|
|
321
|
+
timed_method.run(harvester_id, n_commands)
|
|
322
|
+
|
|
323
|
+
tmp_logger.debug("Done")
|
|
324
|
+
|
|
325
|
+
# Getting the commands timed out
|
|
326
|
+
if timed_method.result == TIME_OUT:
|
|
327
|
+
return generate_response(False, message=TIME_OUT)
|
|
328
|
+
|
|
329
|
+
# Unpack the return code and the commands
|
|
330
|
+
return_code, commands = timed_method.result
|
|
331
|
+
|
|
332
|
+
# There was an error retrieving the commands from the database
|
|
333
|
+
if return_code == -1:
|
|
334
|
+
return generate_response(False, message=MESSAGE_DATABASE)
|
|
335
|
+
|
|
336
|
+
return generate_response(True, data=commands)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
340
|
+
def acknowledge_commands(req: PandaRequest, command_ids: List, timeout: int = 30) -> Dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Acknowledge harvester commands.
|
|
343
|
+
|
|
344
|
+
Acknowledges the list of command IDs in the PanDA database. Requires a secure connection and production role.
|
|
345
|
+
|
|
346
|
+
API details:
|
|
347
|
+
HTTP Method: POST
|
|
348
|
+
Path: /v1/harvester/acknowledge_commands
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
req(PandaRequest): The request object containing the environment variables.
|
|
352
|
+
command_ids(list): A list of command IDs to acknowledge, e.g. `[1, 2, 3, 4,...]`.
|
|
353
|
+
timeout(int, optional): The timeout value. Defaults to `30`.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
357
|
+
"""
|
|
358
|
+
tmp_logger = LogWrapper(_logger, "acknowledge_commands")
|
|
359
|
+
tmp_logger.debug("Start")
|
|
360
|
+
|
|
361
|
+
timed_method = TimedMethod(global_task_buffer.ackCommands, timeout)
|
|
362
|
+
timed_method.run(command_ids)
|
|
363
|
+
|
|
364
|
+
tmp_logger.debug("Done")
|
|
365
|
+
|
|
366
|
+
# Make response
|
|
367
|
+
if timed_method.result == TIME_OUT:
|
|
368
|
+
return generate_response(False, message=TIME_OUT)
|
|
369
|
+
|
|
370
|
+
# Unpack the return code and the commands
|
|
371
|
+
return_code = timed_method.result
|
|
372
|
+
|
|
373
|
+
# There was an error acknowledging the commands in the database
|
|
374
|
+
if return_code == -1:
|
|
375
|
+
return generate_response(False, message=MESSAGE_DATABASE)
|
|
376
|
+
|
|
377
|
+
return generate_response(True)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
381
|
+
def add_sweep_command(req: PandaRequest, panda_queue: str, status_list: List[str], ce_list: List[str], submission_host_list: List[str]) -> Dict[str, Any]:
|
|
382
|
+
"""
|
|
383
|
+
Add sweep command for harvester.
|
|
384
|
+
|
|
385
|
+
Send a command to harvester to kill the workers in a PanDA queue, with the possibility of specifying filters by status, CE or submission host. Requires a secure connection and production role.
|
|
386
|
+
|
|
387
|
+
API details:
|
|
388
|
+
HTTP Method: POST
|
|
389
|
+
Path: /v1/harvester/add_sweep_command
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
req(PandaRequest): internally generated request object
|
|
393
|
+
panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
|
|
394
|
+
status_list (list): list of worker statuses to be considered, e.g. `['submitted', 'running']`
|
|
395
|
+
ce_list (list): list of the Computing Elements to be considered, e.g. `['ce1.cern.ch', 'ce2.cern.ch']`
|
|
396
|
+
submission_host_list(list): list of the harvester submission hosts to be considered, e.g. `['submission_host1.cern.ch', 'submission_host2.cern.ch']`
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
tmp_logger = LogWrapper(_logger, f"add_sweep_command panda_queue={panda_queue}")
|
|
403
|
+
tmp_logger.debug("Start")
|
|
404
|
+
return_message = global_task_buffer.sweepPQ(panda_queue, status_list, ce_list, submission_host_list)
|
|
405
|
+
if return_message == "OK":
|
|
406
|
+
success, message = True, ""
|
|
407
|
+
else:
|
|
408
|
+
success, message = False, return_message
|
|
409
|
+
tmp_logger.debug("Done")
|
|
410
|
+
return generate_response(success, message=message)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
414
|
+
def add_target_slots(req, panda_queue: str, slots: int, global_share: str = None, resource_type: str = None, expiration_date: str = None):
|
|
415
|
+
"""
|
|
416
|
+
Set target slots.
|
|
417
|
+
|
|
418
|
+
Set the target number of slots for a PanDA queue, when you want to build up job pressure. Requires secure connection and production role.
|
|
419
|
+
|
|
420
|
+
API details:
|
|
421
|
+
HTTP Method: POST
|
|
422
|
+
Path: /v1/harvester/add_target_slots
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
req (PandaRequest): Internally generated request object.
|
|
426
|
+
panda_queue(str): Name of the PanDA queue, e.g. `CERN`.
|
|
427
|
+
slots (int): Number of slots to set, e.g. `10000`.
|
|
428
|
+
global_share (str, optional): Global share the slots apply to. Optional - by default it applies to the whole queue. E.g. `User Analysis`
|
|
429
|
+
resource_type (str, optional): Resource type the slots apply to. Optional - by default it applies to the whole queue. E.g. `SCORE` or `MCORE`.
|
|
430
|
+
expiration_date (str, optional): The expiration date of the slots. Optional - by default it applies indefinitely.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
dict: dictionary `{'success': True/False, 'message': 'Description of error', 'data': <requested data>}`
|
|
434
|
+
"""
|
|
435
|
+
tmp_logger = LogWrapper(_logger, f"add_target_slots panda_queue={panda_queue}")
|
|
436
|
+
tmp_logger.debug(f"Start with slots={slots}, global_share={global_share}, resource_type={resource_type}, expiration_date={expiration_date}")
|
|
437
|
+
return_code, return_message = global_task_buffer.setNumSlotsForWP(panda_queue, slots, global_share, resource_type, expiration_date)
|
|
438
|
+
|
|
439
|
+
if return_code == 0:
|
|
440
|
+
success, message = True, return_message
|
|
441
|
+
else:
|
|
442
|
+
success, message = False, return_message
|
|
443
|
+
|
|
444
|
+
tmp_logger.debug("Done")
|
|
445
|
+
return generate_response(success, message=message)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
client methods
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from pandacommon.pandautils.net_utils import replace_hostname_in_url_randomly
|
|
9
|
+
|
|
10
|
+
# PanDA server configuration
|
|
11
|
+
base_url = os.environ.get("PANDA_URL", "http://pandaserver.cern.ch:25080/server/panda")
|
|
12
|
+
base_url_ssl = os.environ.get("PANDA_URL_SSL", "https://pandaserver.cern.ch:25443/server/panda")
|
|
13
|
+
|
|
14
|
+
api_url = os.environ.get("PANDA_API_URL", "http://pandaserver.cern.ch:25080/api/v1")
|
|
15
|
+
api_url_ssl = os.environ.get("PANDA_API_URL_SSL", "https://pandaserver.cern.ch:25443/api/v1")
|
|
16
|
+
|
|
17
|
+
# exit code
|
|
18
|
+
EC_Failed = 255
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_https(url):
|
|
22
|
+
# check if https is used
|
|
23
|
+
return url.startswith("https://")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HttpClient:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
# verification of the host certificate
|
|
29
|
+
if "PANDA_VERIFY_HOST" in os.environ and os.environ["PANDA_VERIFY_HOST"] == "off":
|
|
30
|
+
self.verifyHost = False
|
|
31
|
+
else:
|
|
32
|
+
self.verifyHost = True
|
|
33
|
+
|
|
34
|
+
# request a compressed response
|
|
35
|
+
self.compress = True
|
|
36
|
+
|
|
37
|
+
# SSL cert/key
|
|
38
|
+
self.ssl_certificate = self._x509()
|
|
39
|
+
self.ssl_key = self._x509()
|
|
40
|
+
|
|
41
|
+
# OIDC
|
|
42
|
+
self.oidc = os.getenv("PANDA_AUTH") == "oidc"
|
|
43
|
+
self.auth_vo = os.getenv("PANDA_AUTH_VO") if self.oidc else None
|
|
44
|
+
self.id_token = os.getenv("PANDA_AUTH_ID_TOKEN") if self.oidc else None
|
|
45
|
+
if self.id_token and self.id_token.startswith("file:"):
|
|
46
|
+
with open(self.id_token[5:], "r") as f:
|
|
47
|
+
self.id_token = f.read().strip()
|
|
48
|
+
|
|
49
|
+
def _x509(self):
|
|
50
|
+
# retrieve the X509_USER_PROXY from the environment variables
|
|
51
|
+
try:
|
|
52
|
+
return os.environ["X509_USER_PROXY"]
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# look for the default place
|
|
57
|
+
x509 = f"/tmp/x509up_u{os.getuid()}"
|
|
58
|
+
if os.access(x509, os.R_OK):
|
|
59
|
+
return x509
|
|
60
|
+
|
|
61
|
+
# no valid proxy certificate
|
|
62
|
+
print("No valid grid proxy certificate found")
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
def _prepare_url(self, url):
|
|
66
|
+
"""Modify URL with HTTPS check and hostname replacement."""
|
|
67
|
+
use_https = is_https(url)
|
|
68
|
+
if "PANDA_BEHIND_REAL_LB" in os.environ:
|
|
69
|
+
modified_url = url
|
|
70
|
+
else:
|
|
71
|
+
modified_url = replace_hostname_in_url_randomly(url)
|
|
72
|
+
return modified_url, use_https
|
|
73
|
+
|
|
74
|
+
def _prepare_headers(self, accept_json=True, content_type_json=True, encoding=None):
|
|
75
|
+
"""Prepare headers based on authentication and JSON settings."""
|
|
76
|
+
headers = {}
|
|
77
|
+
if accept_json:
|
|
78
|
+
headers["Accept"] = "application/json"
|
|
79
|
+
if content_type_json:
|
|
80
|
+
headers["Content-Type"] = "application/json"
|
|
81
|
+
|
|
82
|
+
if encoding:
|
|
83
|
+
headers["Content-Encoding"] = encoding
|
|
84
|
+
|
|
85
|
+
if self.oidc:
|
|
86
|
+
headers["Authorization"] = f"Bearer {self.id_token}"
|
|
87
|
+
headers["Origin"] = self.auth_vo
|
|
88
|
+
|
|
89
|
+
return headers
|
|
90
|
+
|
|
91
|
+
def _prepare_ssl(self, use_https):
|
|
92
|
+
"""Prepare SSL configuration based on HTTPS usage and verification settings."""
|
|
93
|
+
cert = None
|
|
94
|
+
verify = True
|
|
95
|
+
if use_https:
|
|
96
|
+
if not self.oidc:
|
|
97
|
+
cert = (self.ssl_certificate, self.ssl_key)
|
|
98
|
+
|
|
99
|
+
if not self.verifyHost:
|
|
100
|
+
verify = False
|
|
101
|
+
elif "X509_CERT_DIR" in os.environ:
|
|
102
|
+
verify = os.environ["X509_CERT_DIR"]
|
|
103
|
+
elif os.path.exists("/etc/grid-security/certificates"):
|
|
104
|
+
verify = "/etc/grid-security/certificates"
|
|
105
|
+
|
|
106
|
+
return cert, verify
|
|
107
|
+
|
|
108
|
+
def get(self, url, data):
|
|
109
|
+
url, use_https = self._prepare_url(url)
|
|
110
|
+
headers = self._prepare_headers()
|
|
111
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
response = requests.get(url, headers=headers, params=data, timeout=600, cert=cert, verify=verify)
|
|
115
|
+
response.raise_for_status()
|
|
116
|
+
return 0, response.json()
|
|
117
|
+
except requests.RequestException as e:
|
|
118
|
+
return 255, str(e)
|
|
119
|
+
|
|
120
|
+
def post(self, url, data):
|
|
121
|
+
url, use_https = self._prepare_url(url)
|
|
122
|
+
headers = self._prepare_headers()
|
|
123
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
response = requests.post(url, headers=headers, json=data, timeout=600, cert=cert, verify=verify)
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
return 0, response.json()
|
|
129
|
+
except requests.RequestException as e:
|
|
130
|
+
return 255, str(e)
|
|
131
|
+
|
|
132
|
+
def post_files(self, url, data, encoding=None):
|
|
133
|
+
url, use_https = self._prepare_url(url)
|
|
134
|
+
headers = self._prepare_headers(content_type_json=False, encoding=encoding)
|
|
135
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
136
|
+
|
|
137
|
+
files = {}
|
|
138
|
+
try:
|
|
139
|
+
for key, value in data.items():
|
|
140
|
+
if isinstance(data[key], str):
|
|
141
|
+
# we got a file to upload without specifying the destination name
|
|
142
|
+
files[key] = open(data[key], "rb")
|
|
143
|
+
else:
|
|
144
|
+
# we got a file to upload which specifies the destination name
|
|
145
|
+
files[key] = (data[key][0], open(data[key][1], "rb"))
|
|
146
|
+
print(f"cert: {cert}, verify: {verify}")
|
|
147
|
+
response = requests.post(url, headers=headers, files=files, timeout=600, cert=cert, verify=verify)
|
|
148
|
+
response.raise_for_status()
|
|
149
|
+
return 0, response.json()
|
|
150
|
+
except requests.RequestException as e:
|
|
151
|
+
return 255, str(e)
|
|
152
|
+
finally:
|
|
153
|
+
for file in files.values():
|
|
154
|
+
if isinstance(file, tuple):
|
|
155
|
+
file_handler = file[1]
|
|
156
|
+
else:
|
|
157
|
+
file_handler = file
|
|
158
|
+
file_handler.close()
|
|
159
|
+
|
|
160
|
+
def override_oidc(self, oidc, id_token, auth_vo):
|
|
161
|
+
"""
|
|
162
|
+
Override OIDC settings.
|
|
163
|
+
"""
|
|
164
|
+
self.oidc = oidc
|
|
165
|
+
self.id_token = id_token
|
|
166
|
+
self.auth_vo = auth_vo
|