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,1625 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dispatch jobs
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import socket
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
import traceback
|
|
15
|
+
from threading import Lock
|
|
16
|
+
|
|
17
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
18
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
19
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
20
|
+
|
|
21
|
+
from pandaserver.brokerage.SiteMapper import SiteMapper
|
|
22
|
+
from pandaserver.config import panda_config
|
|
23
|
+
from pandaserver.dataservice.adder_gen import AdderGen
|
|
24
|
+
from pandaserver.jobdispatcher import Protocol
|
|
25
|
+
from pandaserver.proxycache import panda_proxy_cache, token_cache
|
|
26
|
+
from pandaserver.srvcore import CoreUtils
|
|
27
|
+
|
|
28
|
+
# logger
|
|
29
|
+
_logger = PandaLogger().getLogger("JobDispatcher")
|
|
30
|
+
_pilotReqLogger = PandaLogger().getLogger("PilotRequests")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# a wrapper to install timeout into a method
|
|
34
|
+
class _TimedMethod:
|
|
35
|
+
def __init__(self, method, timeout):
|
|
36
|
+
self.method = method
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.result = Protocol.TimeOutToken
|
|
39
|
+
|
|
40
|
+
# method emulation
|
|
41
|
+
def __call__(self, *var):
|
|
42
|
+
self.result = self.method(*var)
|
|
43
|
+
|
|
44
|
+
# run
|
|
45
|
+
def run(self, *var):
|
|
46
|
+
thr = threading.Thread(target=self, args=var)
|
|
47
|
+
thr.start()
|
|
48
|
+
thr.join()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# job dispatcher
|
|
52
|
+
class JobDispatcher:
|
|
53
|
+
# constructor
|
|
54
|
+
def __init__(self):
|
|
55
|
+
# taskbuffer
|
|
56
|
+
self.taskBuffer = None
|
|
57
|
+
# datetime of last updated
|
|
58
|
+
self.lastUpdated = naive_utcnow()
|
|
59
|
+
# how frequently update DN/token map
|
|
60
|
+
self.timeInterval = datetime.timedelta(seconds=180)
|
|
61
|
+
# special dispatcher parameters
|
|
62
|
+
self.specialDispatchParams = None
|
|
63
|
+
# site mapper cache
|
|
64
|
+
self.siteMapperCache = None
|
|
65
|
+
# lock
|
|
66
|
+
self.lock = Lock()
|
|
67
|
+
# proxy cacher
|
|
68
|
+
self.proxy_cacher = panda_proxy_cache.MyProxyInterface()
|
|
69
|
+
# token cacher
|
|
70
|
+
self.token_cacher = token_cache.TokenCache()
|
|
71
|
+
# config of token cacher
|
|
72
|
+
try:
|
|
73
|
+
with open(panda_config.token_cache_config) as f:
|
|
74
|
+
self.token_cache_config = json.load(f)
|
|
75
|
+
except Exception:
|
|
76
|
+
self.token_cache_config = {}
|
|
77
|
+
|
|
78
|
+
# set task buffer
|
|
79
|
+
def init(self, taskBuffer):
|
|
80
|
+
# lock
|
|
81
|
+
self.lock.acquire()
|
|
82
|
+
# set TB
|
|
83
|
+
if self.taskBuffer is None:
|
|
84
|
+
self.taskBuffer = taskBuffer
|
|
85
|
+
# special dispatcher parameters
|
|
86
|
+
if self.specialDispatchParams is None:
|
|
87
|
+
self.specialDispatchParams = CoreUtils.CachedObject("dispatcher_params", 60 * 10, self.get_special_dispatch_params, _logger)
|
|
88
|
+
# site mapper cache
|
|
89
|
+
if self.siteMapperCache is None:
|
|
90
|
+
self.siteMapperCache = CoreUtils.CachedObject("site_mapper", 60 * 10, self.getSiteMapper, _logger)
|
|
91
|
+
# release
|
|
92
|
+
self.lock.release()
|
|
93
|
+
|
|
94
|
+
# get special parameters for dispatcher
|
|
95
|
+
def get_special_dispatch_params(self):
|
|
96
|
+
"""
|
|
97
|
+
Wrapper function around taskBuffer.get_special_dispatch_params to convert list to set since task buffer cannot return set
|
|
98
|
+
"""
|
|
99
|
+
param = self.taskBuffer.get_special_dispatch_params()
|
|
100
|
+
for client_name in param["tokenKeys"]:
|
|
101
|
+
param["tokenKeys"][client_name]["fullList"] = set(param["tokenKeys"][client_name]["fullList"])
|
|
102
|
+
return True, param
|
|
103
|
+
|
|
104
|
+
# set user proxy
|
|
105
|
+
def set_user_proxy(self, response, distinguished_name=None, role=None, tokenized=False) -> tuple[bool, str]:
|
|
106
|
+
"""
|
|
107
|
+
Set user proxy to the response
|
|
108
|
+
|
|
109
|
+
:param response: response object
|
|
110
|
+
:param distinguished_name: the distinguished name of the user
|
|
111
|
+
:param role: the role of the user
|
|
112
|
+
:param tokenized: whether the response should contain a token instead of a proxy
|
|
113
|
+
|
|
114
|
+
:return: a tuple containing a boolean indicating success and a message
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
if distinguished_name is None:
|
|
118
|
+
distinguished_name = response.data["prodUserID"]
|
|
119
|
+
# remove redundant extensions
|
|
120
|
+
distinguished_name = CoreUtils.get_bare_dn(distinguished_name, keep_digits=False)
|
|
121
|
+
if not tokenized:
|
|
122
|
+
# get proxy
|
|
123
|
+
output = self.proxy_cacher.retrieve(distinguished_name, role=role)
|
|
124
|
+
else:
|
|
125
|
+
# get token
|
|
126
|
+
output = self.token_cacher.get_access_token(distinguished_name)
|
|
127
|
+
# not found
|
|
128
|
+
if output is None:
|
|
129
|
+
tmp_msg = f"""{"token" if tokenized else "proxy"} not found for {distinguished_name}"""
|
|
130
|
+
response.appendNode("errorDialog", tmp_msg)
|
|
131
|
+
return False, tmp_msg
|
|
132
|
+
# set
|
|
133
|
+
response.appendNode("userProxy", output)
|
|
134
|
+
return True, ""
|
|
135
|
+
except Exception as e:
|
|
136
|
+
tmp_msg = f"""{"token" if tokenized else "proxy"} retrieval failed with {str(e)}"""
|
|
137
|
+
response.appendNode("errorDialog", tmp_msg)
|
|
138
|
+
return False, tmp_msg
|
|
139
|
+
|
|
140
|
+
# get job
|
|
141
|
+
def getJob(
|
|
142
|
+
self,
|
|
143
|
+
siteName,
|
|
144
|
+
prodSourceLabel,
|
|
145
|
+
mem,
|
|
146
|
+
diskSpace,
|
|
147
|
+
node,
|
|
148
|
+
timeout,
|
|
149
|
+
computingElement,
|
|
150
|
+
prodUserID,
|
|
151
|
+
getProxyKey,
|
|
152
|
+
realDN,
|
|
153
|
+
taskID,
|
|
154
|
+
nJobs,
|
|
155
|
+
acceptJson,
|
|
156
|
+
background,
|
|
157
|
+
resourceType,
|
|
158
|
+
harvester_id,
|
|
159
|
+
worker_id,
|
|
160
|
+
schedulerID,
|
|
161
|
+
jobType,
|
|
162
|
+
via_topic,
|
|
163
|
+
remaining_time,
|
|
164
|
+
tmpLog,
|
|
165
|
+
):
|
|
166
|
+
t_getJob_start = time.time()
|
|
167
|
+
jobs = []
|
|
168
|
+
try:
|
|
169
|
+
tmpNumJobs = int(nJobs)
|
|
170
|
+
except Exception:
|
|
171
|
+
tmpNumJobs = None
|
|
172
|
+
if tmpNumJobs is None:
|
|
173
|
+
tmpNumJobs = 1
|
|
174
|
+
|
|
175
|
+
self.siteMapperCache.update()
|
|
176
|
+
is_gu = self.siteMapperCache.cachedObj.getSite(siteName).is_grandly_unified()
|
|
177
|
+
in_test = self.siteMapperCache.cachedObj.getSite(siteName).status == "test"
|
|
178
|
+
|
|
179
|
+
# change label
|
|
180
|
+
if in_test and prodSourceLabel in ["user", "managed", "unified"]:
|
|
181
|
+
new_label = "test"
|
|
182
|
+
tmpLog.debug(f"prodSourceLabel changed {prodSourceLabel} -> {new_label}")
|
|
183
|
+
prodSourceLabel = new_label
|
|
184
|
+
|
|
185
|
+
# wrapper function for timeout
|
|
186
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.getJobs, timeout)
|
|
187
|
+
tmpWrapper.run(
|
|
188
|
+
tmpNumJobs,
|
|
189
|
+
siteName,
|
|
190
|
+
prodSourceLabel,
|
|
191
|
+
mem,
|
|
192
|
+
diskSpace,
|
|
193
|
+
node,
|
|
194
|
+
timeout,
|
|
195
|
+
computingElement,
|
|
196
|
+
prodUserID,
|
|
197
|
+
taskID,
|
|
198
|
+
background,
|
|
199
|
+
resourceType,
|
|
200
|
+
harvester_id,
|
|
201
|
+
worker_id,
|
|
202
|
+
schedulerID,
|
|
203
|
+
jobType,
|
|
204
|
+
is_gu,
|
|
205
|
+
via_topic,
|
|
206
|
+
remaining_time,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if isinstance(tmpWrapper.result, list):
|
|
210
|
+
jobs = jobs + tmpWrapper.result
|
|
211
|
+
# make response
|
|
212
|
+
secrets_map = {}
|
|
213
|
+
if len(jobs) > 0:
|
|
214
|
+
secrets_map = jobs.pop()
|
|
215
|
+
proxyKey = jobs[-1]
|
|
216
|
+
nSent = jobs[-2]
|
|
217
|
+
jobs = jobs[:-2]
|
|
218
|
+
if len(jobs) != 0:
|
|
219
|
+
# succeed
|
|
220
|
+
responseList = []
|
|
221
|
+
# append Jobs
|
|
222
|
+
for tmpJob in jobs:
|
|
223
|
+
try:
|
|
224
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
225
|
+
response.appendJob(tmpJob, self.siteMapperCache)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
tmpMsg = f"failed to get jobs with {str(e)}"
|
|
228
|
+
tmpLog.error(tmpMsg + "\n" + traceback.format_exc())
|
|
229
|
+
raise
|
|
230
|
+
|
|
231
|
+
# append nSent
|
|
232
|
+
response.appendNode("nSent", nSent)
|
|
233
|
+
# set proxy key
|
|
234
|
+
if getProxyKey:
|
|
235
|
+
response.setProxyKey(proxyKey)
|
|
236
|
+
# set secrets
|
|
237
|
+
if tmpJob.use_secrets() and tmpJob.prodUserName in secrets_map and secrets_map[tmpJob.prodUserName]:
|
|
238
|
+
response.appendNode("secrets", secrets_map[tmpJob.prodUserName])
|
|
239
|
+
if panda_config.pilot_secrets in secrets_map and secrets_map[panda_config.pilot_secrets]:
|
|
240
|
+
response.appendNode("pilotSecrets", secrets_map[panda_config.pilot_secrets])
|
|
241
|
+
# add
|
|
242
|
+
responseList.append(response.data)
|
|
243
|
+
# make response for bulk
|
|
244
|
+
if nJobs is not None:
|
|
245
|
+
try:
|
|
246
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
247
|
+
if not acceptJson:
|
|
248
|
+
response.appendNode("jobs", json.dumps(responseList))
|
|
249
|
+
else:
|
|
250
|
+
response.appendNode("jobs", responseList)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
tmpMsg = f"failed to make response with {str(e)}"
|
|
253
|
+
tmpLog.error(tmpMsg + "\n" + traceback.format_exc())
|
|
254
|
+
raise
|
|
255
|
+
|
|
256
|
+
else:
|
|
257
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
258
|
+
# timeout
|
|
259
|
+
if acceptJson:
|
|
260
|
+
response = Protocol.Response(Protocol.SC_TimeOut, "database timeout")
|
|
261
|
+
else:
|
|
262
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
263
|
+
else:
|
|
264
|
+
# no available jobs
|
|
265
|
+
if acceptJson:
|
|
266
|
+
response = Protocol.Response(Protocol.SC_NoJobs, "no jobs in PanDA")
|
|
267
|
+
else:
|
|
268
|
+
response = Protocol.Response(Protocol.SC_NoJobs)
|
|
269
|
+
_pilotReqLogger.info(f"method=noJob,site={siteName},node={node},type={prodSourceLabel}")
|
|
270
|
+
# return
|
|
271
|
+
tmpLog.debug(f"{siteName} {node} ret -> {response.encode(acceptJson)}")
|
|
272
|
+
|
|
273
|
+
t_getJob_end = time.time()
|
|
274
|
+
t_getJob_spent = t_getJob_end - t_getJob_start
|
|
275
|
+
tmpLog.info(f"siteName={siteName} took timing={t_getJob_spent}s in_test={in_test}")
|
|
276
|
+
return response.encode(acceptJson)
|
|
277
|
+
|
|
278
|
+
# update job status
|
|
279
|
+
def updateJob(
|
|
280
|
+
self,
|
|
281
|
+
jobID,
|
|
282
|
+
jobStatus,
|
|
283
|
+
timeout,
|
|
284
|
+
xml,
|
|
285
|
+
siteName,
|
|
286
|
+
param,
|
|
287
|
+
metadata,
|
|
288
|
+
pilotLog,
|
|
289
|
+
attemptNr=None,
|
|
290
|
+
stdout="",
|
|
291
|
+
acceptJson=False,
|
|
292
|
+
):
|
|
293
|
+
tmp_logger = LogWrapper(_logger, f"updateJob {jobID}")
|
|
294
|
+
|
|
295
|
+
# store the pilot log
|
|
296
|
+
if pilotLog != "":
|
|
297
|
+
tmp_logger.debug("saving pilot log")
|
|
298
|
+
try:
|
|
299
|
+
self.taskBuffer.storePilotLog(int(jobID), pilotLog)
|
|
300
|
+
tmp_logger.debug("saving pilot log DONE")
|
|
301
|
+
except Exception:
|
|
302
|
+
tmp_logger.debug("saving pilot log FAILED")
|
|
303
|
+
|
|
304
|
+
# add metadata
|
|
305
|
+
if metadata != "":
|
|
306
|
+
ret = self.taskBuffer.addMetadata([jobID], [metadata], [jobStatus])
|
|
307
|
+
if len(ret) > 0 and not ret[0]:
|
|
308
|
+
tmp_logger.debug(f"failed to add metadata")
|
|
309
|
+
# return succeed
|
|
310
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
311
|
+
return response.encode(acceptJson)
|
|
312
|
+
|
|
313
|
+
# add stdout
|
|
314
|
+
if stdout != "":
|
|
315
|
+
self.taskBuffer.addStdOut(jobID, stdout)
|
|
316
|
+
|
|
317
|
+
# update the job
|
|
318
|
+
tmpStatus = jobStatus
|
|
319
|
+
updateStateChange = False
|
|
320
|
+
if jobStatus == "failed" or jobStatus == "finished":
|
|
321
|
+
tmpStatus = "holding"
|
|
322
|
+
# update stateChangeTime to prevent Watcher from finding this job
|
|
323
|
+
updateStateChange = True
|
|
324
|
+
param["jobDispatcherErrorDiag"] = None
|
|
325
|
+
elif jobStatus in ["holding", "transferring"]:
|
|
326
|
+
param["jobDispatcherErrorDiag"] = f"set to {jobStatus} by the pilot at {naive_utcnow().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
327
|
+
if tmpStatus == "holding":
|
|
328
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.updateJobStatus, None)
|
|
329
|
+
else:
|
|
330
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.updateJobStatus, timeout)
|
|
331
|
+
tmpWrapper.run(jobID, tmpStatus, param, updateStateChange, attemptNr)
|
|
332
|
+
|
|
333
|
+
# make response
|
|
334
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
335
|
+
# timeout
|
|
336
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
337
|
+
else:
|
|
338
|
+
if tmpWrapper.result:
|
|
339
|
+
# succeed
|
|
340
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
341
|
+
result = tmpWrapper.result
|
|
342
|
+
secrets = None
|
|
343
|
+
if isinstance(result, dict):
|
|
344
|
+
if "secrets" in result:
|
|
345
|
+
secrets = result["secrets"]
|
|
346
|
+
result = result["command"]
|
|
347
|
+
# set command
|
|
348
|
+
if isinstance(result, str):
|
|
349
|
+
response.appendNode("command", result)
|
|
350
|
+
if secrets:
|
|
351
|
+
response.appendNode("pilotSecrets", secrets)
|
|
352
|
+
else:
|
|
353
|
+
response.appendNode("command", "NULL")
|
|
354
|
+
|
|
355
|
+
# add output to dataset
|
|
356
|
+
if result not in ["badattemptnr", "alreadydone"] and (jobStatus == "failed" or jobStatus == "finished"):
|
|
357
|
+
adder_gen = AdderGen(self.taskBuffer, jobID, jobStatus, attemptNr)
|
|
358
|
+
adder_gen.dump_file_report(xml, attemptNr)
|
|
359
|
+
del adder_gen
|
|
360
|
+
else:
|
|
361
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
362
|
+
|
|
363
|
+
tmp_logger.debug(f"ret -> {response.encode(acceptJson)}")
|
|
364
|
+
return response.encode(acceptJson)
|
|
365
|
+
|
|
366
|
+
# get job status
|
|
367
|
+
def getStatus(self, strIDs, timeout):
|
|
368
|
+
# convert str to list
|
|
369
|
+
ids = strIDs.split()
|
|
370
|
+
# peek jobs
|
|
371
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.peekJobs, timeout)
|
|
372
|
+
tmpWrapper.run(ids, False, True, True, False)
|
|
373
|
+
# make response
|
|
374
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
375
|
+
# timeout
|
|
376
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
377
|
+
else:
|
|
378
|
+
if isinstance(tmpWrapper.result, list):
|
|
379
|
+
# succeed
|
|
380
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
381
|
+
# make return
|
|
382
|
+
retStr = ""
|
|
383
|
+
attStr = ""
|
|
384
|
+
for job in tmpWrapper.result:
|
|
385
|
+
if job is None:
|
|
386
|
+
retStr += f"notfound+"
|
|
387
|
+
attStr += "0+"
|
|
388
|
+
else:
|
|
389
|
+
retStr += f"{job.jobStatus}+"
|
|
390
|
+
attStr += f"{job.attemptNr}+"
|
|
391
|
+
response.appendNode("status", retStr[:-1])
|
|
392
|
+
response.appendNode("attemptNr", attStr[:-1])
|
|
393
|
+
else:
|
|
394
|
+
# failed
|
|
395
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
396
|
+
_logger.debug(f"getStatus : {strIDs} ret -> {response.encode()}")
|
|
397
|
+
return response.encode()
|
|
398
|
+
|
|
399
|
+
# check job status
|
|
400
|
+
def checkJobStatus(self, pandaIDs, timeout):
|
|
401
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.checkJobStatus, timeout)
|
|
402
|
+
tmpWrapper.run(pandaIDs)
|
|
403
|
+
# make response
|
|
404
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
405
|
+
# timeout
|
|
406
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
407
|
+
else:
|
|
408
|
+
if isinstance(tmpWrapper.result, list):
|
|
409
|
+
# succeed
|
|
410
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
411
|
+
response.appendNode("data", tmpWrapper.result)
|
|
412
|
+
else:
|
|
413
|
+
# failed
|
|
414
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
415
|
+
_logger.debug(f"checkJobStatus : {pandaIDs} ret -> {response.encode(True)}")
|
|
416
|
+
return response.encode(True)
|
|
417
|
+
|
|
418
|
+
# get a list of event ranges for a PandaID
|
|
419
|
+
def getEventRanges(
|
|
420
|
+
self,
|
|
421
|
+
pandaID,
|
|
422
|
+
jobsetID,
|
|
423
|
+
jediTaskID,
|
|
424
|
+
nRanges,
|
|
425
|
+
timeout,
|
|
426
|
+
acceptJson,
|
|
427
|
+
scattered,
|
|
428
|
+
segment_id,
|
|
429
|
+
):
|
|
430
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.getEventRanges, timeout)
|
|
431
|
+
tmpWrapper.run(pandaID, jobsetID, jediTaskID, nRanges, acceptJson, scattered, segment_id)
|
|
432
|
+
# make response
|
|
433
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
434
|
+
# timeout
|
|
435
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
436
|
+
else:
|
|
437
|
+
if tmpWrapper.result is not None:
|
|
438
|
+
# succeed
|
|
439
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
440
|
+
# make return
|
|
441
|
+
response.appendNode("eventRanges", tmpWrapper.result)
|
|
442
|
+
else:
|
|
443
|
+
# failed
|
|
444
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
445
|
+
_logger.debug(f"getEventRanges : {pandaID} ret -> {response.encode(acceptJson)}")
|
|
446
|
+
return response.encode(acceptJson)
|
|
447
|
+
|
|
448
|
+
# update an event range
|
|
449
|
+
def updateEventRange(
|
|
450
|
+
self,
|
|
451
|
+
eventRangeID,
|
|
452
|
+
eventStatus,
|
|
453
|
+
coreCount,
|
|
454
|
+
cpuConsumptionTime,
|
|
455
|
+
objstoreID,
|
|
456
|
+
timeout,
|
|
457
|
+
):
|
|
458
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.updateEventRange, timeout)
|
|
459
|
+
tmpWrapper.run(eventRangeID, eventStatus, coreCount, cpuConsumptionTime, objstoreID)
|
|
460
|
+
# make response
|
|
461
|
+
_logger.debug(str(tmpWrapper.result))
|
|
462
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
463
|
+
# timeout
|
|
464
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
465
|
+
else:
|
|
466
|
+
if tmpWrapper.result[0] is True:
|
|
467
|
+
# succeed
|
|
468
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
469
|
+
response.appendNode("Command", tmpWrapper.result[1])
|
|
470
|
+
else:
|
|
471
|
+
# failed
|
|
472
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
473
|
+
_logger.debug(f"updateEventRange : {eventRangeID} ret -> {response.encode()}")
|
|
474
|
+
return response.encode()
|
|
475
|
+
|
|
476
|
+
# update event ranges
|
|
477
|
+
def updateEventRanges(self, eventRanges, timeout, acceptJson, version):
|
|
478
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.updateEventRanges, timeout)
|
|
479
|
+
tmpWrapper.run(eventRanges, version)
|
|
480
|
+
# make response
|
|
481
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
482
|
+
# timeout
|
|
483
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
484
|
+
else:
|
|
485
|
+
# succeed
|
|
486
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
487
|
+
# make return
|
|
488
|
+
response.appendNode("Returns", tmpWrapper.result[0])
|
|
489
|
+
response.appendNode("Command", tmpWrapper.result[1])
|
|
490
|
+
_logger.debug(f"updateEventRanges : ret -> {response.encode(acceptJson)}")
|
|
491
|
+
return response.encode(acceptJson)
|
|
492
|
+
|
|
493
|
+
# check event availability
|
|
494
|
+
def checkEventsAvailability(self, pandaID, jobsetID, jediTaskID, timeout):
|
|
495
|
+
tmpWrapper = _TimedMethod(self.taskBuffer.checkEventsAvailability, timeout)
|
|
496
|
+
tmpWrapper.run(pandaID, jobsetID, jediTaskID)
|
|
497
|
+
# make response
|
|
498
|
+
if tmpWrapper.result == Protocol.TimeOutToken:
|
|
499
|
+
# timeout
|
|
500
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
501
|
+
else:
|
|
502
|
+
if tmpWrapper.result is not None:
|
|
503
|
+
# succeed
|
|
504
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
505
|
+
# make return
|
|
506
|
+
response.appendNode("nEventRanges", tmpWrapper.result)
|
|
507
|
+
else:
|
|
508
|
+
# failed
|
|
509
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
510
|
+
_logger.debug(f"checkEventsAvailability : {pandaID} ret -> {response.encode(True)}")
|
|
511
|
+
return response.encode(True)
|
|
512
|
+
|
|
513
|
+
# get key pair
|
|
514
|
+
def getKeyPair(self, realDN, publicKeyName, privateKeyName, acceptJson):
|
|
515
|
+
tmpMsg = f"getKeyPair {publicKeyName}/{privateKeyName} json={acceptJson}: "
|
|
516
|
+
if realDN is None:
|
|
517
|
+
# cannot extract DN
|
|
518
|
+
tmpMsg += "failed since DN cannot be extracted"
|
|
519
|
+
_logger.debug(tmpMsg)
|
|
520
|
+
response = Protocol.Response(Protocol.SC_Perms, "Cannot extract DN from proxy. not HTTPS?")
|
|
521
|
+
else:
|
|
522
|
+
# get compact DN
|
|
523
|
+
compactDN = CoreUtils.clean_user_id(realDN)
|
|
524
|
+
# check permission
|
|
525
|
+
self.specialDispatchParams.update()
|
|
526
|
+
allowKey = self.specialDispatchParams.get("allowKeyPair", [])
|
|
527
|
+
if compactDN not in allowKey:
|
|
528
|
+
# permission denied
|
|
529
|
+
tmpMsg += f"failed since '{compactDN}' not authorized with 'k' in {panda_config.schemaMETA}.USERS.GRIDPREF"
|
|
530
|
+
_logger.debug(tmpMsg)
|
|
531
|
+
response = Protocol.Response(Protocol.SC_Perms, tmpMsg)
|
|
532
|
+
else:
|
|
533
|
+
# look for key pair
|
|
534
|
+
if "keyPair" not in self.specialDispatchParams:
|
|
535
|
+
keyPair = {}
|
|
536
|
+
else:
|
|
537
|
+
keyPair = self.specialDispatchParams["keyPair"]
|
|
538
|
+
notFound = False
|
|
539
|
+
if publicKeyName not in keyPair:
|
|
540
|
+
# public key is missing
|
|
541
|
+
notFound = True
|
|
542
|
+
tmpMsg += f"failed for '{compactDN}' since {publicKeyName} is missing on {socket.getfqdn()}"
|
|
543
|
+
elif privateKeyName not in keyPair:
|
|
544
|
+
# private key is missing
|
|
545
|
+
notFound = True
|
|
546
|
+
tmpMsg += f"failed for '{compactDN}' since {privateKeyName} is missing on {socket.getfqdn()}"
|
|
547
|
+
if notFound:
|
|
548
|
+
# private or public key is missing
|
|
549
|
+
_logger.debug(tmpMsg)
|
|
550
|
+
response = Protocol.Response(Protocol.SC_MissKey, tmpMsg)
|
|
551
|
+
else:
|
|
552
|
+
# key pair is available
|
|
553
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
554
|
+
response.appendNode("publicKey", keyPair[publicKeyName])
|
|
555
|
+
response.appendNode("privateKey", keyPair[privateKeyName])
|
|
556
|
+
tmpMsg += f"sent key-pair to '{compactDN}'"
|
|
557
|
+
_logger.debug(tmpMsg)
|
|
558
|
+
# return
|
|
559
|
+
return response.encode(acceptJson)
|
|
560
|
+
|
|
561
|
+
# get a token key
|
|
562
|
+
def get_token_key(self, distinguished_name, client_name, accept_json):
|
|
563
|
+
tmp_log = LogWrapper(_logger, f"get_token_key client={client_name} PID={os.getpid()}")
|
|
564
|
+
if distinguished_name is None:
|
|
565
|
+
# cannot extract DN
|
|
566
|
+
tmp_msg = "failed since DN cannot be extracted. non-HTTPS?"
|
|
567
|
+
tmp_log.debug(tmp_msg)
|
|
568
|
+
response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
|
|
569
|
+
else:
|
|
570
|
+
# get compact DN
|
|
571
|
+
compact_name = CoreUtils.clean_user_id(distinguished_name)
|
|
572
|
+
# check permission
|
|
573
|
+
self.specialDispatchParams.update()
|
|
574
|
+
allowed_users = self.specialDispatchParams.get("allowTokenKey", [])
|
|
575
|
+
if compact_name not in allowed_users:
|
|
576
|
+
# permission denied
|
|
577
|
+
tmp_msg = f"denied since '{compact_name}' not authorized with 't' in {panda_config.schemaMETA}.USERS.GRIDPREF"
|
|
578
|
+
tmp_log.debug(tmp_msg)
|
|
579
|
+
response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
|
|
580
|
+
else:
|
|
581
|
+
# get a token key
|
|
582
|
+
if client_name not in self.specialDispatchParams["tokenKeys"]:
|
|
583
|
+
# token key is missing
|
|
584
|
+
tmp_msg = f"token key is missing for '{client_name}'"
|
|
585
|
+
tmp_log.debug(tmp_msg)
|
|
586
|
+
response = Protocol.Response(Protocol.SC_MissKey, tmp_msg)
|
|
587
|
+
else:
|
|
588
|
+
# token key is available
|
|
589
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
590
|
+
response.appendNode("tokenKey", self.specialDispatchParams["tokenKeys"][client_name]["latest"])
|
|
591
|
+
tmp_msg = f"sent token key to '{compact_name}'"
|
|
592
|
+
tmp_log.debug(tmp_msg)
|
|
593
|
+
# return
|
|
594
|
+
return response.encode(accept_json)
|
|
595
|
+
|
|
596
|
+
# get site mapper
|
|
597
|
+
def getSiteMapper(self):
|
|
598
|
+
return True, SiteMapper(self.taskBuffer)
|
|
599
|
+
|
|
600
|
+
def getCommands(self, harvester_id, n_commands, timeout, accept_json):
|
|
601
|
+
"""
|
|
602
|
+
Get commands for a particular harvester instance
|
|
603
|
+
"""
|
|
604
|
+
tmp_wrapper = _TimedMethod(self.taskBuffer.getCommands, timeout)
|
|
605
|
+
tmp_wrapper.run(harvester_id, n_commands)
|
|
606
|
+
|
|
607
|
+
# Make response
|
|
608
|
+
if tmp_wrapper.result == Protocol.TimeOutToken:
|
|
609
|
+
# timeout
|
|
610
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
611
|
+
else:
|
|
612
|
+
# success
|
|
613
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
614
|
+
response.appendNode("Returns", tmp_wrapper.result[0])
|
|
615
|
+
response.appendNode("Commands", tmp_wrapper.result[1])
|
|
616
|
+
|
|
617
|
+
_logger.debug(f"getCommands : ret -> {response.encode(accept_json)}")
|
|
618
|
+
return response.encode(accept_json)
|
|
619
|
+
|
|
620
|
+
def ackCommands(self, command_ids, timeout, accept_json):
|
|
621
|
+
"""
|
|
622
|
+
Acknowledge the commands from a list of IDs
|
|
623
|
+
"""
|
|
624
|
+
_logger.debug(f"command_ids : {command_ids}")
|
|
625
|
+
tmp_wrapper = _TimedMethod(self.taskBuffer.ackCommands, timeout)
|
|
626
|
+
tmp_wrapper.run(command_ids)
|
|
627
|
+
|
|
628
|
+
# Make response
|
|
629
|
+
if tmp_wrapper.result == Protocol.TimeOutToken:
|
|
630
|
+
# timeout
|
|
631
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
632
|
+
else:
|
|
633
|
+
# success
|
|
634
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
635
|
+
response.appendNode("Returns", tmp_wrapper.result)
|
|
636
|
+
|
|
637
|
+
_logger.debug(f"ackCommands : ret -> {response.encode(accept_json)}")
|
|
638
|
+
return response.encode(accept_json)
|
|
639
|
+
|
|
640
|
+
def getResourceTypes(self, timeout, accept_json):
|
|
641
|
+
"""
|
|
642
|
+
Get resource types (SCORE, MCORE, SCORE_HIMEM, MCORE_HIMEM) and their definitions
|
|
643
|
+
"""
|
|
644
|
+
tmp_wrapper = _TimedMethod(self.taskBuffer.getResourceTypes, timeout)
|
|
645
|
+
tmp_wrapper.run()
|
|
646
|
+
|
|
647
|
+
# Make response
|
|
648
|
+
if tmp_wrapper.result == Protocol.TimeOutToken:
|
|
649
|
+
# timeout
|
|
650
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
651
|
+
else:
|
|
652
|
+
# success
|
|
653
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
654
|
+
response.appendNode("Returns", 0)
|
|
655
|
+
response.appendNode("ResourceTypes", tmp_wrapper.result)
|
|
656
|
+
|
|
657
|
+
_logger.debug(f"getResourceTypes : ret -> {response.encode(accept_json)}")
|
|
658
|
+
return response.encode(accept_json)
|
|
659
|
+
|
|
660
|
+
# get proxy
|
|
661
|
+
def get_proxy(self, real_distinguished_name: str, role: str | None, target_distinguished_name: str | None, tokenized: bool, token_key: str | None) -> dict:
|
|
662
|
+
"""
|
|
663
|
+
Get proxy for a user with a role
|
|
664
|
+
|
|
665
|
+
:param real_distinguished_name: actual distinguished name of the user
|
|
666
|
+
:param role: role of the user
|
|
667
|
+
:param target_distinguished_name: target distinguished name if the user wants to get proxy for someone else.
|
|
668
|
+
This is one of client_name defined in token_cache_config when getting a token
|
|
669
|
+
:param tokenized: whether the response should contain a token instead of a proxy
|
|
670
|
+
:param token_key: key to get the token from the token cache
|
|
671
|
+
|
|
672
|
+
:return: response in dictionary
|
|
673
|
+
"""
|
|
674
|
+
if target_distinguished_name is None:
|
|
675
|
+
target_distinguished_name = real_distinguished_name
|
|
676
|
+
tmp_log = LogWrapper(_logger, f"get_proxy PID={os.getpid()}")
|
|
677
|
+
tmp_msg = f"""start DN="{real_distinguished_name}" role={role} target="{target_distinguished_name}" tokenized={tokenized} token_key={token_key}"""
|
|
678
|
+
tmp_log.debug(tmp_msg)
|
|
679
|
+
if real_distinguished_name is None:
|
|
680
|
+
# cannot extract DN
|
|
681
|
+
tmp_msg = "failed since DN cannot be extracted"
|
|
682
|
+
tmp_log.debug(tmp_msg)
|
|
683
|
+
response = Protocol.Response(Protocol.SC_Perms, "Cannot extract DN from proxy. not HTTPS?")
|
|
684
|
+
else:
|
|
685
|
+
# get compact DN
|
|
686
|
+
compact_name = CoreUtils.clean_user_id(real_distinguished_name)
|
|
687
|
+
# check permission
|
|
688
|
+
self.specialDispatchParams.update()
|
|
689
|
+
if "allowProxy" not in self.specialDispatchParams:
|
|
690
|
+
allowed_names = []
|
|
691
|
+
else:
|
|
692
|
+
allowed_names = self.specialDispatchParams["allowProxy"]
|
|
693
|
+
if compact_name not in allowed_names:
|
|
694
|
+
# permission denied
|
|
695
|
+
tmp_msg = f"failed since '{compact_name}' not in the authorized user list who have 'p' in {panda_config.schemaMETA}.USERS.GRIDPREF "
|
|
696
|
+
if not tokenized:
|
|
697
|
+
tmp_msg += "to get proxy"
|
|
698
|
+
else:
|
|
699
|
+
tmp_msg += "to get access token"
|
|
700
|
+
tmp_log.debug(tmp_msg)
|
|
701
|
+
response = Protocol.Response(Protocol.SC_Perms, tmp_msg)
|
|
702
|
+
elif (
|
|
703
|
+
tokenized
|
|
704
|
+
and target_distinguished_name in self.token_cache_config
|
|
705
|
+
and self.token_cache_config[target_distinguished_name].get("use_token_key") is True
|
|
706
|
+
and (
|
|
707
|
+
target_distinguished_name not in self.specialDispatchParams["tokenKeys"]
|
|
708
|
+
or token_key not in self.specialDispatchParams["tokenKeys"][target_distinguished_name]["fullList"]
|
|
709
|
+
)
|
|
710
|
+
):
|
|
711
|
+
# invalid token key
|
|
712
|
+
tmp_msg = f"failed since token key is invalid for {target_distinguished_name}"
|
|
713
|
+
tmp_log.debug(tmp_msg)
|
|
714
|
+
response = Protocol.Response(Protocol.SC_Invalid, tmp_msg)
|
|
715
|
+
else:
|
|
716
|
+
# get proxy
|
|
717
|
+
response = Protocol.Response(Protocol.SC_Success, "")
|
|
718
|
+
tmp_status, tmp_msg = self.set_user_proxy(response, target_distinguished_name, role, tokenized)
|
|
719
|
+
if not tmp_status:
|
|
720
|
+
tmp_log.debug(tmp_msg)
|
|
721
|
+
response.appendNode("StatusCode", Protocol.SC_ProxyError)
|
|
722
|
+
else:
|
|
723
|
+
tmp_msg = "successful sent proxy"
|
|
724
|
+
tmp_log.debug(tmp_msg)
|
|
725
|
+
# return
|
|
726
|
+
return response.encode(True)
|
|
727
|
+
|
|
728
|
+
# get active job attribute
|
|
729
|
+
def getActiveJobAttributes(self, pandaID, attrs):
|
|
730
|
+
return self.taskBuffer.getActiveJobAttributes(pandaID, attrs)
|
|
731
|
+
|
|
732
|
+
# update job status
|
|
733
|
+
def updateWorkerPilotStatus(self, workerID, harvesterID, status, timeout, accept_json, node_id):
|
|
734
|
+
tmp_wrapper = _TimedMethod(self.taskBuffer.updateWorkerPilotStatus, timeout)
|
|
735
|
+
tmp_wrapper.run(workerID, harvesterID, status, node_id)
|
|
736
|
+
|
|
737
|
+
# make response
|
|
738
|
+
if tmp_wrapper.result == Protocol.TimeOutToken:
|
|
739
|
+
response = Protocol.Response(Protocol.SC_TimeOut)
|
|
740
|
+
else:
|
|
741
|
+
if tmp_wrapper.result: # success
|
|
742
|
+
response = Protocol.Response(Protocol.SC_Success)
|
|
743
|
+
else: # error
|
|
744
|
+
response = Protocol.Response(Protocol.SC_Failed)
|
|
745
|
+
_logger.debug(f"updateWorkerPilotStatus : {workerID} {harvesterID} {status} ret -> {response.encode(accept_json)}")
|
|
746
|
+
return response.encode(accept_json)
|
|
747
|
+
|
|
748
|
+
# get max workerID
|
|
749
|
+
def get_max_worker_id(self, harvester_id):
|
|
750
|
+
id = self.taskBuffer.get_max_worker_id(harvester_id)
|
|
751
|
+
return json.dumps(id)
|
|
752
|
+
|
|
753
|
+
def get_events_status(self, ids):
|
|
754
|
+
ret = self.taskBuffer.get_events_status(ids)
|
|
755
|
+
return json.dumps(ret)
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# Singleton
|
|
759
|
+
jobDispatcher = JobDispatcher()
|
|
760
|
+
del JobDispatcher
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
# get FQANs
|
|
764
|
+
def _getFQAN(req):
|
|
765
|
+
fqans = []
|
|
766
|
+
for tmp_key in req.subprocess_env:
|
|
767
|
+
tmp_value = req.subprocess_env[tmp_key]
|
|
768
|
+
# Scan VOMS attributes
|
|
769
|
+
# compact style
|
|
770
|
+
if tmp_key.startswith("GRST_CRED_") and tmp_value.startswith("VOMS"):
|
|
771
|
+
fqan = tmp_value.split()[-1]
|
|
772
|
+
fqans.append(fqan)
|
|
773
|
+
|
|
774
|
+
# old style
|
|
775
|
+
elif tmp_key.startswith("GRST_CONN_"):
|
|
776
|
+
tmp_items = tmp_value.split(":")
|
|
777
|
+
if len(tmp_items) == 2 and tmp_items[0] == "fqan":
|
|
778
|
+
fqans.append(tmp_items[-1])
|
|
779
|
+
|
|
780
|
+
return fqans
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
# check role
|
|
784
|
+
def _checkRole(fqans, dn, withVomsPatch=True):
|
|
785
|
+
production_manager = False
|
|
786
|
+
try:
|
|
787
|
+
# VOMS attributes of production and pilot roles
|
|
788
|
+
production_attributes = [
|
|
789
|
+
"/atlas/usatlas/Role=production",
|
|
790
|
+
"/atlas/usatlas/Role=pilot",
|
|
791
|
+
"/atlas/Role=production",
|
|
792
|
+
"/atlas/Role=pilot",
|
|
793
|
+
"/osg/Role=pilot",
|
|
794
|
+
"^/[^/]+/Role=production$",
|
|
795
|
+
"/ams/Role=pilot",
|
|
796
|
+
"/Engage/LBNE/Role=pilot",
|
|
797
|
+
]
|
|
798
|
+
if withVomsPatch:
|
|
799
|
+
# FIXME once http://savannah.cern.ch/bugs/?47136 is solved
|
|
800
|
+
production_attributes += ["/atlas/", "/osg/", "/cms/", "/ams/", "/Engage/LBNE/"]
|
|
801
|
+
|
|
802
|
+
for fqan in fqans:
|
|
803
|
+
# check atlas/usatlas production role
|
|
804
|
+
if any(fqan.startswith(role_pattern) or re.search(role_pattern, fqan) for role_pattern in production_attributes):
|
|
805
|
+
production_manager = True
|
|
806
|
+
break
|
|
807
|
+
|
|
808
|
+
# escape
|
|
809
|
+
if production_manager:
|
|
810
|
+
break
|
|
811
|
+
|
|
812
|
+
# check DN with pilot owners
|
|
813
|
+
if not production_manager and dn not in [None]:
|
|
814
|
+
for owner in set(panda_config.production_dns).union(panda_config.pilot_owners):
|
|
815
|
+
if owner and re.search(owner, dn) is not None:
|
|
816
|
+
production_manager = True
|
|
817
|
+
break
|
|
818
|
+
|
|
819
|
+
except Exception:
|
|
820
|
+
pass
|
|
821
|
+
|
|
822
|
+
return production_manager
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
# get DN
|
|
826
|
+
def _getDN(req):
|
|
827
|
+
realDN = None
|
|
828
|
+
if "SSL_CLIENT_S_DN" in req.subprocess_env:
|
|
829
|
+
realDN = req.subprocess_env["SSL_CLIENT_S_DN"]
|
|
830
|
+
# remove redundant CN
|
|
831
|
+
realDN = CoreUtils.get_bare_dn(realDN, keep_proxy=True)
|
|
832
|
+
# return
|
|
833
|
+
return realDN
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
"""
|
|
837
|
+
web service interface
|
|
838
|
+
|
|
839
|
+
"""
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
# get job
|
|
843
|
+
def getJob(
|
|
844
|
+
req,
|
|
845
|
+
siteName,
|
|
846
|
+
token=None,
|
|
847
|
+
timeout=60,
|
|
848
|
+
cpu=None,
|
|
849
|
+
mem=None,
|
|
850
|
+
diskSpace=None,
|
|
851
|
+
prodSourceLabel=None,
|
|
852
|
+
node=None,
|
|
853
|
+
computingElement=None,
|
|
854
|
+
AtlasRelease=None,
|
|
855
|
+
prodUserID=None,
|
|
856
|
+
getProxyKey=None,
|
|
857
|
+
countryGroup=None,
|
|
858
|
+
workingGroup=None,
|
|
859
|
+
allowOtherCountry=None,
|
|
860
|
+
taskID=None,
|
|
861
|
+
nJobs=None,
|
|
862
|
+
background=None,
|
|
863
|
+
resourceType=None,
|
|
864
|
+
harvester_id=None,
|
|
865
|
+
worker_id=None,
|
|
866
|
+
schedulerID=None,
|
|
867
|
+
jobType=None,
|
|
868
|
+
viaTopic=None,
|
|
869
|
+
remaining_time=None,
|
|
870
|
+
):
|
|
871
|
+
tmpLog = LogWrapper(_logger, f"getJob {naive_utcnow().isoformat('/')}")
|
|
872
|
+
tmpLog.debug(siteName)
|
|
873
|
+
# get DN
|
|
874
|
+
realDN = _getDN(req)
|
|
875
|
+
# get FQANs
|
|
876
|
+
fqans = _getFQAN(req)
|
|
877
|
+
# check production role
|
|
878
|
+
if getProxyKey == "True":
|
|
879
|
+
# don't use /atlas to prevent normal proxy getting credname
|
|
880
|
+
prodManager = _checkRole(fqans, realDN, False)
|
|
881
|
+
else:
|
|
882
|
+
prodManager = _checkRole(fqans, realDN)
|
|
883
|
+
# set DN for non-production user
|
|
884
|
+
if not prodManager:
|
|
885
|
+
prodUserID = realDN
|
|
886
|
+
# allow getProxyKey for production role
|
|
887
|
+
if getProxyKey == "True" and prodManager:
|
|
888
|
+
getProxyKey = True
|
|
889
|
+
else:
|
|
890
|
+
getProxyKey = False
|
|
891
|
+
# convert mem, diskSpace, remaining_time
|
|
892
|
+
try:
|
|
893
|
+
mem = int(float(mem))
|
|
894
|
+
if mem < 0:
|
|
895
|
+
mem = 0
|
|
896
|
+
except Exception:
|
|
897
|
+
mem = 0
|
|
898
|
+
try:
|
|
899
|
+
diskSpace = int(float(diskSpace))
|
|
900
|
+
if diskSpace < 0:
|
|
901
|
+
diskSpace = 0
|
|
902
|
+
except Exception:
|
|
903
|
+
diskSpace = 0
|
|
904
|
+
try:
|
|
905
|
+
remaining_time = max(0, int(remaining_time))
|
|
906
|
+
except Exception:
|
|
907
|
+
remaining_time = 0
|
|
908
|
+
if background == "True":
|
|
909
|
+
background = True
|
|
910
|
+
else:
|
|
911
|
+
background = False
|
|
912
|
+
if viaTopic == "True":
|
|
913
|
+
viaTopic = True
|
|
914
|
+
else:
|
|
915
|
+
viaTopic = False
|
|
916
|
+
tmpLog.debug(
|
|
917
|
+
f"{siteName},nJobs={nJobs},cpu={cpu},mem={mem},disk={diskSpace},source_label={prodSourceLabel},"
|
|
918
|
+
f"node={node},ce={computingElement},rel={AtlasRelease},user={prodUserID},proxy={getProxyKey},"
|
|
919
|
+
f"c_group={countryGroup},w_group={workingGroup},{allowOtherCountry},taskID={taskID},DN={realDN},"
|
|
920
|
+
f"role={prodManager},FQAN={str(fqans)},json={req.acceptJson()},"
|
|
921
|
+
f"bg={background},rt={resourceType},harvester_id={harvester_id},worker_id={worker_id},"
|
|
922
|
+
f"schedulerID={schedulerID},jobType={jobType},viaTopic={viaTopic} remaining_time={remaining_time}"
|
|
923
|
+
)
|
|
924
|
+
try:
|
|
925
|
+
dummyNumSlots = int(nJobs)
|
|
926
|
+
except Exception:
|
|
927
|
+
dummyNumSlots = 1
|
|
928
|
+
if dummyNumSlots > 1:
|
|
929
|
+
for iSlots in range(dummyNumSlots):
|
|
930
|
+
_pilotReqLogger.info(f"method=getJob,site={siteName},node={node}_{iSlots},type={prodSourceLabel}")
|
|
931
|
+
else:
|
|
932
|
+
_pilotReqLogger.info(f"method=getJob,site={siteName},node={node},type={prodSourceLabel}")
|
|
933
|
+
# invalid role
|
|
934
|
+
if (not prodManager) and (prodSourceLabel not in ["user"]):
|
|
935
|
+
tmpLog.warning("invalid role")
|
|
936
|
+
if req.acceptJson():
|
|
937
|
+
tmpMsg = "no production/pilot role in VOMS FQANs or non pilot owner"
|
|
938
|
+
else:
|
|
939
|
+
tmpMsg = None
|
|
940
|
+
return Protocol.Response(Protocol.SC_Role, tmpMsg).encode(req.acceptJson())
|
|
941
|
+
# invoke job dispatcher
|
|
942
|
+
return jobDispatcher.getJob(
|
|
943
|
+
siteName,
|
|
944
|
+
prodSourceLabel,
|
|
945
|
+
mem,
|
|
946
|
+
diskSpace,
|
|
947
|
+
node,
|
|
948
|
+
int(timeout),
|
|
949
|
+
computingElement,
|
|
950
|
+
prodUserID,
|
|
951
|
+
getProxyKey,
|
|
952
|
+
realDN,
|
|
953
|
+
taskID,
|
|
954
|
+
nJobs,
|
|
955
|
+
req.acceptJson(),
|
|
956
|
+
background,
|
|
957
|
+
resourceType,
|
|
958
|
+
harvester_id,
|
|
959
|
+
worker_id,
|
|
960
|
+
schedulerID,
|
|
961
|
+
jobType,
|
|
962
|
+
viaTopic,
|
|
963
|
+
remaining_time,
|
|
964
|
+
tmpLog,
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
# update job status
|
|
969
|
+
def updateJob(
|
|
970
|
+
req,
|
|
971
|
+
jobId,
|
|
972
|
+
state,
|
|
973
|
+
token=None,
|
|
974
|
+
transExitCode=None,
|
|
975
|
+
pilotErrorCode=None,
|
|
976
|
+
pilotErrorDiag=None,
|
|
977
|
+
timestamp=None,
|
|
978
|
+
timeout=60,
|
|
979
|
+
xml="",
|
|
980
|
+
node=None,
|
|
981
|
+
workdir=None,
|
|
982
|
+
cpuConsumptionTime=None,
|
|
983
|
+
cpuConsumptionUnit=None,
|
|
984
|
+
remainingSpace=None,
|
|
985
|
+
schedulerID=None,
|
|
986
|
+
pilotID=None,
|
|
987
|
+
siteName=None,
|
|
988
|
+
messageLevel=None,
|
|
989
|
+
pilotLog="",
|
|
990
|
+
metaData="",
|
|
991
|
+
cpuConversionFactor=None,
|
|
992
|
+
exeErrorCode=None,
|
|
993
|
+
exeErrorDiag=None,
|
|
994
|
+
pilotTiming=None,
|
|
995
|
+
computingElement=None,
|
|
996
|
+
startTime=None,
|
|
997
|
+
endTime=None,
|
|
998
|
+
nEvents=None,
|
|
999
|
+
nInputFiles=None,
|
|
1000
|
+
batchID=None,
|
|
1001
|
+
attemptNr=None,
|
|
1002
|
+
jobMetrics=None,
|
|
1003
|
+
stdout="",
|
|
1004
|
+
jobSubStatus=None,
|
|
1005
|
+
coreCount=None,
|
|
1006
|
+
maxRSS=None,
|
|
1007
|
+
maxVMEM=None,
|
|
1008
|
+
maxSWAP=None,
|
|
1009
|
+
maxPSS=None,
|
|
1010
|
+
avgRSS=None,
|
|
1011
|
+
avgVMEM=None,
|
|
1012
|
+
avgSWAP=None,
|
|
1013
|
+
avgPSS=None,
|
|
1014
|
+
totRCHAR=None,
|
|
1015
|
+
totWCHAR=None,
|
|
1016
|
+
totRBYTES=None,
|
|
1017
|
+
totWBYTES=None,
|
|
1018
|
+
rateRCHAR=None,
|
|
1019
|
+
rateWCHAR=None,
|
|
1020
|
+
rateRBYTES=None,
|
|
1021
|
+
rateWBYTES=None,
|
|
1022
|
+
corruptedFiles=None,
|
|
1023
|
+
meanCoreCount=None,
|
|
1024
|
+
cpu_architecture_level=None,
|
|
1025
|
+
grid=None,
|
|
1026
|
+
source_site=None,
|
|
1027
|
+
destination_site=None,
|
|
1028
|
+
):
|
|
1029
|
+
tmp_log = LogWrapper(_logger, f"updateJob PandaID={jobId} PID={os.getpid()}")
|
|
1030
|
+
tmp_log.debug("start")
|
|
1031
|
+
|
|
1032
|
+
# get DN, FQANs and roles
|
|
1033
|
+
realDN = _getDN(req)
|
|
1034
|
+
fqans = _getFQAN(req)
|
|
1035
|
+
prodManager = _checkRole(fqans, realDN)
|
|
1036
|
+
acceptJson = req.acceptJson()
|
|
1037
|
+
|
|
1038
|
+
_logger.debug(
|
|
1039
|
+
f"updateJob({jobId},status={state},transExitCode={transExitCode},pilotErrorCode={pilotErrorCode},pilotErrorDiag={pilotErrorDiag},node={node},workdir={workdir},"
|
|
1040
|
+
f"cpuConsumptionTime={cpuConsumptionTime},cpuConsumptionUnit={cpuConsumptionUnit},cpu_architecture_level={cpu_architecture_level},remainingSpace={remainingSpace},"
|
|
1041
|
+
f"schedulerID={schedulerID},pilotID={pilotID},siteName={siteName},messageLevel={messageLevel},nEvents={nEvents},nInputFiles={nInputFiles},cpuConversionFactor={cpuConversionFactor},"
|
|
1042
|
+
f"exeErrorCode={exeErrorCode},exeErrorDiag={exeErrorDiag},pilotTiming={pilotTiming},computingElement={computingElement},startTime={startTime},endTime={endTime},batchID={batchID},"
|
|
1043
|
+
f"attemptNr:{attemptNr},jobSubStatus:{jobSubStatus},core:{coreCount},DN:{realDN},role:{prodManager},"
|
|
1044
|
+
f"FQAN:{fqans},maxRSS={maxRSS},maxVMEM={maxVMEM},maxSWAP={maxSWAP},"
|
|
1045
|
+
f"maxPSS={maxPSS},avgRSS={avgRSS},avgVMEM={avgVMEM},avgSWAP={avgSWAP},avgPSS={avgPSS},"
|
|
1046
|
+
f"totRCHAR={totRCHAR},totWCHAR={totWCHAR},totRBYTES={totRBYTES},totWBYTES={totWBYTES},rateRCHAR={rateRCHAR},"
|
|
1047
|
+
f"rateWCHAR={rateWCHAR},rateRBYTES={rateRBYTES},rateWBYTES={rateWBYTES},meanCoreCount={meanCoreCount},"
|
|
1048
|
+
f"grid={grid},source_site={source_site},destination_site={destination_site},"
|
|
1049
|
+
f"corruptedFiles={corruptedFiles}\n==XML==\n{xml}\n==LOG==\n{pilotLog[:1024]}\n==Meta==\n{metaData[:1024]}\n"
|
|
1050
|
+
f"==Metrics==\n{jobMetrics}\n==stdout==\n{stdout})"
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
_pilotReqLogger.debug(f"method=updateJob,site={siteName},node={node},type=None")
|
|
1054
|
+
|
|
1055
|
+
# invalid role
|
|
1056
|
+
if not prodManager:
|
|
1057
|
+
tmp_log.warning(f"invalid role")
|
|
1058
|
+
tmpMsg = None
|
|
1059
|
+
if acceptJson:
|
|
1060
|
+
tmpMsg = "no production/pilot role in VOMS FQANs or non pilot owner"
|
|
1061
|
+
return Protocol.Response(Protocol.SC_Role, tmpMsg).encode(acceptJson)
|
|
1062
|
+
|
|
1063
|
+
# aborting message
|
|
1064
|
+
if jobId == "NULL":
|
|
1065
|
+
return Protocol.Response(Protocol.SC_Success).encode(acceptJson)
|
|
1066
|
+
|
|
1067
|
+
# check status
|
|
1068
|
+
if state not in [
|
|
1069
|
+
"running",
|
|
1070
|
+
"failed",
|
|
1071
|
+
"finished",
|
|
1072
|
+
"holding",
|
|
1073
|
+
"starting",
|
|
1074
|
+
"transferring",
|
|
1075
|
+
]:
|
|
1076
|
+
tmp_log.warning(f"invalid state={state} for updateJob")
|
|
1077
|
+
return Protocol.Response(Protocol.SC_Success).encode(acceptJson)
|
|
1078
|
+
|
|
1079
|
+
# create parameter map
|
|
1080
|
+
param = {}
|
|
1081
|
+
if cpuConsumptionTime not in [None, ""]:
|
|
1082
|
+
param["cpuConsumptionTime"] = cpuConsumptionTime
|
|
1083
|
+
if cpuConsumptionUnit is not None:
|
|
1084
|
+
param["cpuConsumptionUnit"] = cpuConsumptionUnit
|
|
1085
|
+
if cpu_architecture_level:
|
|
1086
|
+
try:
|
|
1087
|
+
param["cpu_architecture_level"] = cpu_architecture_level[:20]
|
|
1088
|
+
except Exception:
|
|
1089
|
+
tmp_log.error(f"invalid cpu_architecture_level={cpu_architecture_level} for updateJob")
|
|
1090
|
+
pass
|
|
1091
|
+
if node is not None:
|
|
1092
|
+
param["modificationHost"] = node[:128]
|
|
1093
|
+
if transExitCode is not None:
|
|
1094
|
+
try:
|
|
1095
|
+
int(transExitCode)
|
|
1096
|
+
param["transExitCode"] = transExitCode
|
|
1097
|
+
except Exception:
|
|
1098
|
+
pass
|
|
1099
|
+
if pilotErrorCode is not None:
|
|
1100
|
+
try:
|
|
1101
|
+
int(pilotErrorCode)
|
|
1102
|
+
param["pilotErrorCode"] = pilotErrorCode
|
|
1103
|
+
except Exception:
|
|
1104
|
+
pass
|
|
1105
|
+
if pilotErrorDiag is not None:
|
|
1106
|
+
param["pilotErrorDiag"] = pilotErrorDiag[:500]
|
|
1107
|
+
if jobMetrics is not None:
|
|
1108
|
+
param["jobMetrics"] = jobMetrics[:500]
|
|
1109
|
+
if schedulerID is not None:
|
|
1110
|
+
param["schedulerID"] = schedulerID
|
|
1111
|
+
if pilotID is not None:
|
|
1112
|
+
param["pilotID"] = pilotID[:200]
|
|
1113
|
+
if batchID is not None:
|
|
1114
|
+
param["batchID"] = batchID[:80]
|
|
1115
|
+
if exeErrorCode is not None:
|
|
1116
|
+
param["exeErrorCode"] = exeErrorCode
|
|
1117
|
+
if exeErrorDiag is not None:
|
|
1118
|
+
param["exeErrorDiag"] = exeErrorDiag[:500]
|
|
1119
|
+
if cpuConversionFactor is not None:
|
|
1120
|
+
param["cpuConversion"] = cpuConversionFactor
|
|
1121
|
+
if pilotTiming is not None:
|
|
1122
|
+
param["pilotTiming"] = pilotTiming
|
|
1123
|
+
if nEvents is not None:
|
|
1124
|
+
param["nEvents"] = nEvents
|
|
1125
|
+
if nInputFiles is not None:
|
|
1126
|
+
param["nInputFiles"] = nInputFiles
|
|
1127
|
+
if jobSubStatus not in [None, ""]:
|
|
1128
|
+
param["jobSubStatus"] = jobSubStatus
|
|
1129
|
+
if coreCount not in [None, ""]:
|
|
1130
|
+
param["actualCoreCount"] = coreCount
|
|
1131
|
+
if meanCoreCount:
|
|
1132
|
+
try:
|
|
1133
|
+
param["meanCoreCount"] = float(meanCoreCount)
|
|
1134
|
+
except Exception:
|
|
1135
|
+
pass
|
|
1136
|
+
if grid is not None:
|
|
1137
|
+
param["grid"] = grid
|
|
1138
|
+
if source_site is not None:
|
|
1139
|
+
param["sourceSite"] = source_site
|
|
1140
|
+
if destination_site is not None:
|
|
1141
|
+
param["destinationSite"] = destination_site
|
|
1142
|
+
if maxRSS is not None:
|
|
1143
|
+
param["maxRSS"] = maxRSS
|
|
1144
|
+
if maxVMEM is not None:
|
|
1145
|
+
param["maxVMEM"] = maxVMEM
|
|
1146
|
+
if maxSWAP is not None:
|
|
1147
|
+
param["maxSWAP"] = maxSWAP
|
|
1148
|
+
if maxPSS is not None:
|
|
1149
|
+
param["maxPSS"] = maxPSS
|
|
1150
|
+
if avgRSS is not None:
|
|
1151
|
+
param["avgRSS"] = int(float(avgRSS))
|
|
1152
|
+
if avgVMEM is not None:
|
|
1153
|
+
param["avgVMEM"] = int(float(avgVMEM))
|
|
1154
|
+
if avgSWAP is not None:
|
|
1155
|
+
param["avgSWAP"] = int(float(avgSWAP))
|
|
1156
|
+
if avgPSS is not None:
|
|
1157
|
+
param["avgPSS"] = int(float(avgPSS))
|
|
1158
|
+
if totRCHAR is not None:
|
|
1159
|
+
totRCHAR = int(totRCHAR) / 1024 # convert to kByte
|
|
1160
|
+
totRCHAR = min(10**10 - 1, totRCHAR) # limit to 10 digit
|
|
1161
|
+
param["totRCHAR"] = totRCHAR
|
|
1162
|
+
if totWCHAR is not None:
|
|
1163
|
+
totWCHAR = int(totWCHAR) / 1024 # convert to kByte
|
|
1164
|
+
totWCHAR = min(10**10 - 1, totWCHAR) # limit to 10 digit
|
|
1165
|
+
param["totWCHAR"] = totWCHAR
|
|
1166
|
+
if totRBYTES is not None:
|
|
1167
|
+
totRBYTES = int(totRBYTES) / 1024 # convert to kByte
|
|
1168
|
+
totRBYTES = min(10**10 - 1, totRBYTES) # limit to 10 digit
|
|
1169
|
+
param["totRBYTES"] = totRBYTES
|
|
1170
|
+
if totWBYTES is not None:
|
|
1171
|
+
totWBYTES = int(totWBYTES) / 1024 # convert to kByte
|
|
1172
|
+
totWBYTES = min(10**10 - 1, totWBYTES) # limit to 10 digit
|
|
1173
|
+
param["totWBYTES"] = totWBYTES
|
|
1174
|
+
if rateRCHAR is not None:
|
|
1175
|
+
rateRCHAR = min(10**10 - 1, int(float(rateRCHAR))) # limit to 10 digit
|
|
1176
|
+
param["rateRCHAR"] = rateRCHAR
|
|
1177
|
+
if rateWCHAR is not None:
|
|
1178
|
+
rateWCHAR = min(10**10 - 1, int(float(rateWCHAR))) # limit to 10 digit
|
|
1179
|
+
param["rateWCHAR"] = rateWCHAR
|
|
1180
|
+
if rateRBYTES is not None:
|
|
1181
|
+
rateRBYTES = min(10**10 - 1, int(float(rateRBYTES))) # limit to 10 digit
|
|
1182
|
+
param["rateRBYTES"] = rateRBYTES
|
|
1183
|
+
if rateWBYTES is not None:
|
|
1184
|
+
rateWBYTES = min(10**10 - 1, int(float(rateWBYTES))) # limit to 10 digit
|
|
1185
|
+
param["rateWBYTES"] = rateWBYTES
|
|
1186
|
+
if startTime is not None:
|
|
1187
|
+
try:
|
|
1188
|
+
param["startTime"] = datetime.datetime(*time.strptime(startTime, "%Y-%m-%d %H:%M:%S")[:6])
|
|
1189
|
+
except Exception:
|
|
1190
|
+
pass
|
|
1191
|
+
if endTime is not None:
|
|
1192
|
+
try:
|
|
1193
|
+
param["endTime"] = datetime.datetime(*time.strptime(endTime, "%Y-%m-%d %H:%M:%S")[:6])
|
|
1194
|
+
except Exception:
|
|
1195
|
+
pass
|
|
1196
|
+
if attemptNr is not None:
|
|
1197
|
+
try:
|
|
1198
|
+
attemptNr = int(attemptNr)
|
|
1199
|
+
except Exception:
|
|
1200
|
+
attemptNr = None
|
|
1201
|
+
if stdout != "":
|
|
1202
|
+
stdout = stdout[:2048]
|
|
1203
|
+
if corruptedFiles is not None:
|
|
1204
|
+
param["corruptedFiles"] = corruptedFiles
|
|
1205
|
+
# invoke JD
|
|
1206
|
+
tmp_log.debug("executing")
|
|
1207
|
+
return jobDispatcher.updateJob(
|
|
1208
|
+
int(jobId),
|
|
1209
|
+
state,
|
|
1210
|
+
int(timeout),
|
|
1211
|
+
xml,
|
|
1212
|
+
siteName,
|
|
1213
|
+
param,
|
|
1214
|
+
metaData,
|
|
1215
|
+
pilotLog,
|
|
1216
|
+
attemptNr,
|
|
1217
|
+
stdout,
|
|
1218
|
+
acceptJson,
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
# bulk update jobs
|
|
1223
|
+
def updateJobsInBulk(req, jobList, harvester_id=None):
|
|
1224
|
+
ret_list = []
|
|
1225
|
+
ret_val = False
|
|
1226
|
+
prefix = f"updateJobsInBulk {harvester_id}"
|
|
1227
|
+
tmp_logger = LogWrapper(_logger, prefix)
|
|
1228
|
+
tmp_logger.debug("start")
|
|
1229
|
+
t_start = naive_utcnow()
|
|
1230
|
+
|
|
1231
|
+
try:
|
|
1232
|
+
job_list = json.loads(jobList)
|
|
1233
|
+
for job_dict in job_list:
|
|
1234
|
+
job_id = job_dict["jobId"]
|
|
1235
|
+
del job_dict["jobId"]
|
|
1236
|
+
state = job_dict["state"]
|
|
1237
|
+
del job_dict["state"]
|
|
1238
|
+
if "metaData" in job_dict:
|
|
1239
|
+
job_dict["metaData"] = str(job_dict["metaData"])
|
|
1240
|
+
tmp_ret = updateJob(req, job_id, state, **job_dict)
|
|
1241
|
+
ret_list.append(tmp_ret)
|
|
1242
|
+
ret_val = True
|
|
1243
|
+
except Exception:
|
|
1244
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
1245
|
+
tmp_msg = f"failed with {err_type.__name__} {err_value}"
|
|
1246
|
+
ret_list = f"{prefix} {tmp_msg}"
|
|
1247
|
+
tmp_logger.error(f"{tmp_msg}\n{traceback.format_exc()}")
|
|
1248
|
+
|
|
1249
|
+
t_delta = naive_utcnow() - t_start
|
|
1250
|
+
tmp_logger.debug(f"took {t_delta.seconds}.{t_delta.microseconds // 1000:03d} sec")
|
|
1251
|
+
return json.dumps((ret_val, ret_list))
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
# get job status
|
|
1255
|
+
def getStatus(req, ids, timeout=60):
|
|
1256
|
+
_logger.debug(f"getStatus({ids})")
|
|
1257
|
+
return jobDispatcher.getStatus(ids, int(timeout))
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
# check job status
|
|
1261
|
+
def checkJobStatus(req, ids, timeout=60):
|
|
1262
|
+
return jobDispatcher.checkJobStatus(ids, int(timeout))
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
# get a list of even ranges for a PandaID
|
|
1266
|
+
def getEventRanges(
|
|
1267
|
+
req,
|
|
1268
|
+
pandaID,
|
|
1269
|
+
jobsetID,
|
|
1270
|
+
taskID=None,
|
|
1271
|
+
nRanges=10,
|
|
1272
|
+
timeout=60,
|
|
1273
|
+
scattered=None,
|
|
1274
|
+
segment_id=None,
|
|
1275
|
+
):
|
|
1276
|
+
"""
|
|
1277
|
+
Check the permissions and retrieve a list of event ranges for a given PandaID.
|
|
1278
|
+
|
|
1279
|
+
Args:
|
|
1280
|
+
req: The request object containing the environment variables.
|
|
1281
|
+
pandaID (str): The ID of the Panda job.
|
|
1282
|
+
jobsetID (str): The ID of the job set.
|
|
1283
|
+
taskID (str, optional): The ID of the task. Defaults to None.
|
|
1284
|
+
nRanges (int, optional): The number of event ranges to retrieve. Defaults to 10.
|
|
1285
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
1286
|
+
scattered (str, optional): Whether the event ranges are scattered. Defaults to None.
|
|
1287
|
+
segment_id (int, optional): The segment ID. Defaults to None.
|
|
1288
|
+
Returns:
|
|
1289
|
+
dict: The response from the job dispatcher.
|
|
1290
|
+
"""
|
|
1291
|
+
tmp_log = LogWrapper(_logger, f"getEventRanges(PandaID={pandaID} jobsetID={jobsetID} taskID={taskID},nRanges={nRanges},segment={segment_id})")
|
|
1292
|
+
tmp_log.debug("start")
|
|
1293
|
+
|
|
1294
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1295
|
+
if not tmp_stat:
|
|
1296
|
+
tmp_log.error(f"failed with {tmp_out}")
|
|
1297
|
+
return tmp_out
|
|
1298
|
+
|
|
1299
|
+
if scattered == "True":
|
|
1300
|
+
scattered = True
|
|
1301
|
+
else:
|
|
1302
|
+
scattered = False
|
|
1303
|
+
|
|
1304
|
+
if segment_id is not None:
|
|
1305
|
+
segment_id = int(segment_id)
|
|
1306
|
+
|
|
1307
|
+
return jobDispatcher.getEventRanges(
|
|
1308
|
+
pandaID,
|
|
1309
|
+
jobsetID,
|
|
1310
|
+
taskID,
|
|
1311
|
+
nRanges,
|
|
1312
|
+
int(timeout),
|
|
1313
|
+
req.acceptJson(),
|
|
1314
|
+
scattered,
|
|
1315
|
+
segment_id,
|
|
1316
|
+
)
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
def updateEventRange(
|
|
1320
|
+
req,
|
|
1321
|
+
eventRangeID,
|
|
1322
|
+
eventStatus,
|
|
1323
|
+
coreCount=None,
|
|
1324
|
+
cpuConsumptionTime=None,
|
|
1325
|
+
objstoreID=None,
|
|
1326
|
+
timeout=60,
|
|
1327
|
+
pandaID=None,
|
|
1328
|
+
):
|
|
1329
|
+
"""
|
|
1330
|
+
Check the permissions and update the status of a specific event range.
|
|
1331
|
+
|
|
1332
|
+
Args:
|
|
1333
|
+
req: The request object containing the environment variables.
|
|
1334
|
+
eventRangeID (str): The ID of the event range to update.
|
|
1335
|
+
eventStatus (str): The new status of the event range.
|
|
1336
|
+
coreCount (int, optional): The number of cores used. Defaults to None.
|
|
1337
|
+
cpuConsumptionTime (float, optional): The CPU consumption time. Defaults to None.
|
|
1338
|
+
objstoreID (int, optional): The object store ID. Defaults to None.
|
|
1339
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
1340
|
+
pandaID (str, optional): The PandaID. Defaults to None.
|
|
1341
|
+
|
|
1342
|
+
Returns:
|
|
1343
|
+
dict: The response from the job dispatcher.
|
|
1344
|
+
"""
|
|
1345
|
+
tmp_log = LogWrapper(
|
|
1346
|
+
_logger, f"updateEventRange({eventRangeID} status={eventStatus} coreCount={coreCount} cpuConsumptionTime={cpuConsumptionTime} osID={objstoreID})"
|
|
1347
|
+
)
|
|
1348
|
+
tmp_log.debug("start")
|
|
1349
|
+
|
|
1350
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1351
|
+
if not tmp_stat:
|
|
1352
|
+
tmp_log.error(f"failed with {tmp_out}")
|
|
1353
|
+
return tmp_out
|
|
1354
|
+
|
|
1355
|
+
return jobDispatcher.updateEventRange(
|
|
1356
|
+
eventRangeID,
|
|
1357
|
+
eventStatus,
|
|
1358
|
+
coreCount,
|
|
1359
|
+
cpuConsumptionTime,
|
|
1360
|
+
objstoreID,
|
|
1361
|
+
int(timeout),
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
|
|
1365
|
+
def updateEventRanges(req, eventRanges, timeout=120, version=0, pandaID=None):
|
|
1366
|
+
"""
|
|
1367
|
+
This function checks the permissions, converts the version to an integer, and updates the event ranges.
|
|
1368
|
+
|
|
1369
|
+
Args:
|
|
1370
|
+
req: The request object containing the environment variables.
|
|
1371
|
+
eventRanges (str): A JSON string containing the list of event ranges to update.
|
|
1372
|
+
timeout (int, optional): The timeout value. Defaults to 120.
|
|
1373
|
+
version (int, optional): The version of the event ranges. Defaults to 0.
|
|
1374
|
+
pandaID (str, optional): The PandaID. Defaults to None.
|
|
1375
|
+
Returns:
|
|
1376
|
+
dict: The response from the job dispatcher.
|
|
1377
|
+
"""
|
|
1378
|
+
tmp_log = LogWrapper(_logger, f"updateEventRanges({eventRanges})")
|
|
1379
|
+
tmp_log.debug("start")
|
|
1380
|
+
|
|
1381
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1382
|
+
if not tmp_stat:
|
|
1383
|
+
tmp_log.error(f"failed with {tmp_out}")
|
|
1384
|
+
return tmp_out
|
|
1385
|
+
|
|
1386
|
+
try:
|
|
1387
|
+
version = int(version)
|
|
1388
|
+
except Exception:
|
|
1389
|
+
version = 0
|
|
1390
|
+
|
|
1391
|
+
return jobDispatcher.updateEventRanges(eventRanges, int(timeout), req.acceptJson(), version)
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
def checkEventsAvailability(req, pandaID, jobsetID, taskID, timeout=60):
|
|
1395
|
+
"""
|
|
1396
|
+
This function checks the availability of events for a given PandaID, jobsetID, and taskID.
|
|
1397
|
+
|
|
1398
|
+
Args:
|
|
1399
|
+
req: The request object containing the environment variables.
|
|
1400
|
+
pandaID (str): The PandaID.
|
|
1401
|
+
jobset_id (str): The jobsetID.
|
|
1402
|
+
task_id (str): The taskID.
|
|
1403
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
1404
|
+
Returns:
|
|
1405
|
+
bool: The availability status of the events.
|
|
1406
|
+
"""
|
|
1407
|
+
tmp_log = LogWrapper(_logger, f"check_events_availability(panda_id={pandaID} jobset_id={jobsetID} task_id={taskID})")
|
|
1408
|
+
tmp_log.debug("start")
|
|
1409
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1410
|
+
if not tmp_stat:
|
|
1411
|
+
tmp_log.error(f"failed with {tmp_out}")
|
|
1412
|
+
|
|
1413
|
+
return jobDispatcher.checkEventsAvailability(pandaID, jobsetID, taskID, timeout)
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
def getKeyPair(req, publicKeyName, privateKeyName):
|
|
1417
|
+
"""
|
|
1418
|
+
This function retrieves the distinguished name (DN) from the request and uses it to get a key pair.
|
|
1419
|
+
|
|
1420
|
+
Args:
|
|
1421
|
+
req: The request object containing the environment variables.
|
|
1422
|
+
publicKeyName (str): The name of the public key.
|
|
1423
|
+
privateKeyName (str): The name of the private key.
|
|
1424
|
+
|
|
1425
|
+
Returns:
|
|
1426
|
+
dict: The key pair for the user.
|
|
1427
|
+
"""
|
|
1428
|
+
real_dn = _getDN(req)
|
|
1429
|
+
return jobDispatcher.getKeyPair(real_dn, publicKeyName, privateKeyName, req.acceptJson())
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def getProxy(req, role=None, dn=None):
|
|
1433
|
+
"""
|
|
1434
|
+
Get proxy for a user with a role.
|
|
1435
|
+
|
|
1436
|
+
Args:
|
|
1437
|
+
req: The request object containing the environment variables.
|
|
1438
|
+
role (str, optional): The role of the user. Defaults to None.
|
|
1439
|
+
dn (str, optional): The distinguished name of the user. Defaults to None.
|
|
1440
|
+
Returns:
|
|
1441
|
+
dict: The proxy for the user.
|
|
1442
|
+
"""
|
|
1443
|
+
real_dn = _getDN(req)
|
|
1444
|
+
if role == "":
|
|
1445
|
+
role = None
|
|
1446
|
+
return jobDispatcher.get_proxy(real_dn, role, dn, False, None)
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
def get_access_token(req, client_name, token_key=None):
|
|
1450
|
+
"""
|
|
1451
|
+
This function retrieves the distinguished name (DN) from the request and uses it to get an access token for the specified client.
|
|
1452
|
+
|
|
1453
|
+
Args:
|
|
1454
|
+
req: The request object containing the environment variables.
|
|
1455
|
+
client_name (str): The name of the client requesting the access token.
|
|
1456
|
+
token_key (str, optional): The key to get the token from the token cache. Defaults to None.
|
|
1457
|
+
Returns:
|
|
1458
|
+
dict: The access token for the client.
|
|
1459
|
+
"""
|
|
1460
|
+
# get DN
|
|
1461
|
+
real_dn = _getDN(req)
|
|
1462
|
+
return jobDispatcher.get_proxy(real_dn, None, client_name, True, token_key)
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def get_token_key(req, client_name):
|
|
1466
|
+
"""
|
|
1467
|
+
This function retrieves the distinguished name (DN) from the request and uses it to get a token key for the specified client.
|
|
1468
|
+
|
|
1469
|
+
Args:
|
|
1470
|
+
req: The request object containing the environment variables.
|
|
1471
|
+
client_name (str): The name of the client requesting the token key.
|
|
1472
|
+
Returns:
|
|
1473
|
+
str: The token key for the client.
|
|
1474
|
+
"""
|
|
1475
|
+
# get DN
|
|
1476
|
+
real_dn = _getDN(req)
|
|
1477
|
+
return jobDispatcher.get_token_key(real_dn, client_name, req.acceptJson())
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
def checkPilotPermission(req):
|
|
1481
|
+
"""
|
|
1482
|
+
This function retrieves the distinguished name (DN) and Fully Qualified Attribute Names (FQANs) from the request,
|
|
1483
|
+
checks if the user has a production role, and verifies the DN.
|
|
1484
|
+
|
|
1485
|
+
Args:
|
|
1486
|
+
req: The request object containing the environment variables.
|
|
1487
|
+
Returns:
|
|
1488
|
+
tuple: A tuple containing a boolean indicating success and a message.
|
|
1489
|
+
"""
|
|
1490
|
+
# get DN
|
|
1491
|
+
real_dn = _getDN(req)
|
|
1492
|
+
if real_dn is None:
|
|
1493
|
+
return False, "failed to retrieve DN"
|
|
1494
|
+
|
|
1495
|
+
# get FQANs and check production role
|
|
1496
|
+
fqans = _getFQAN(req)
|
|
1497
|
+
prod_manager = _checkRole(fqans, real_dn, True)
|
|
1498
|
+
if not prod_manager:
|
|
1499
|
+
return False, "production or pilot role is required"
|
|
1500
|
+
|
|
1501
|
+
return True, None
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
def getCommands(req, harvester_id, n_commands, timeout=30):
|
|
1505
|
+
"""
|
|
1506
|
+
This function checks the permissions and retrieves the commands for a specified harvester instance.
|
|
1507
|
+
|
|
1508
|
+
Args:
|
|
1509
|
+
req: The request object containing the environment variables.
|
|
1510
|
+
harvester_id (str): The ID of the harvester instance.
|
|
1511
|
+
n_commands (int): The number of commands to retrieve.
|
|
1512
|
+
timeout (int, optional): The timeout value. Defaults to 30.
|
|
1513
|
+
Returns:
|
|
1514
|
+
dict: The response from the job dispatcher.
|
|
1515
|
+
"""
|
|
1516
|
+
tmp_str = "getCommands"
|
|
1517
|
+
|
|
1518
|
+
# check permissions
|
|
1519
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1520
|
+
if not tmp_stat:
|
|
1521
|
+
_logger.error(f"{tmp_str} failed with {tmp_out}")
|
|
1522
|
+
|
|
1523
|
+
accept_json = req.acceptJson()
|
|
1524
|
+
# retrieve the commands
|
|
1525
|
+
return jobDispatcher.getCommands(harvester_id, n_commands, timeout, accept_json)
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def ackCommands(req, command_ids, timeout=30):
|
|
1529
|
+
"""
|
|
1530
|
+
This function checks the permissions, parses the command IDs from JSON, and acknowledges the list of commands.
|
|
1531
|
+
|
|
1532
|
+
Args:
|
|
1533
|
+
req: The request object containing the environment variables.
|
|
1534
|
+
command_ids (str): A JSON string containing the list of command IDs to acknowledge.
|
|
1535
|
+
timeout (int, optional): The timeout value. Defaults to 30.
|
|
1536
|
+
Returns:
|
|
1537
|
+
dict: The response from the job dispatcher.
|
|
1538
|
+
"""
|
|
1539
|
+
tmp_str = "ackCommands"
|
|
1540
|
+
|
|
1541
|
+
# check permissions
|
|
1542
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1543
|
+
if not tmp_stat:
|
|
1544
|
+
_logger.error(f"{tmp_str} failed with {tmp_out}")
|
|
1545
|
+
|
|
1546
|
+
command_ids = json.loads(command_ids)
|
|
1547
|
+
accept_json = req.acceptJson()
|
|
1548
|
+
|
|
1549
|
+
# retrieve the commands
|
|
1550
|
+
return jobDispatcher.ackCommands(command_ids, timeout, accept_json)
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
def getResourceTypes(req, timeout=30):
|
|
1554
|
+
"""
|
|
1555
|
+
This function retrieves the resource types (MCORE, SCORE, etc.) and their definitions.
|
|
1556
|
+
|
|
1557
|
+
Args:
|
|
1558
|
+
req: The request object containing the environment variables.
|
|
1559
|
+
timeout (int, optional): The timeout value. Defaults to 30.
|
|
1560
|
+
Returns:
|
|
1561
|
+
dict: The resource types and their definitions.
|
|
1562
|
+
"""
|
|
1563
|
+
tmp_str = "getResourceTypes"
|
|
1564
|
+
|
|
1565
|
+
# check permissions
|
|
1566
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1567
|
+
if not tmp_stat:
|
|
1568
|
+
_logger.error(f"{tmp_str} failed with {tmp_out}")
|
|
1569
|
+
|
|
1570
|
+
accept_json = req.acceptJson()
|
|
1571
|
+
|
|
1572
|
+
# retrieve the resource types
|
|
1573
|
+
return jobDispatcher.getResourceTypes(timeout, accept_json)
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
def updateWorkerPilotStatus(req, site, workerID, harvesterID, status, timeout=60, node_id=None):
|
|
1577
|
+
"""
|
|
1578
|
+
Update the status of a worker according to the pilot.
|
|
1579
|
+
|
|
1580
|
+
This function validates the pilot permissions and the state passed by the pilot, then updates the worker status.
|
|
1581
|
+
|
|
1582
|
+
Args:
|
|
1583
|
+
req: The request object containing the environment variables.
|
|
1584
|
+
site (str): The site name.
|
|
1585
|
+
workerID (str): The worker ID.
|
|
1586
|
+
harvesterID (str): The harvester ID.
|
|
1587
|
+
status (str): The status of the worker. Must be either 'started' or 'finished'.
|
|
1588
|
+
timeout (int, optional): The timeout value. Defaults to 60.
|
|
1589
|
+
node_id (str, optional): The node ID. Defaults to None.
|
|
1590
|
+
|
|
1591
|
+
Returns:
|
|
1592
|
+
str: The result of the status update or an error message.
|
|
1593
|
+
"""
|
|
1594
|
+
tmp_log = LogWrapper(
|
|
1595
|
+
_logger,
|
|
1596
|
+
f"updateWorkerPilotStatus workerID={workerID} harvesterID={harvesterID} status={status} nodeID={node_id} PID={os.getpid()}",
|
|
1597
|
+
)
|
|
1598
|
+
tmp_log.debug("start")
|
|
1599
|
+
|
|
1600
|
+
# validate the pilot permissions
|
|
1601
|
+
tmp_stat, tmp_out = checkPilotPermission(req)
|
|
1602
|
+
if not tmp_stat:
|
|
1603
|
+
tmp_log.error(f"failed with {tmp_out}")
|
|
1604
|
+
return tmp_out
|
|
1605
|
+
|
|
1606
|
+
# validate the state passed by the pilot
|
|
1607
|
+
valid_states = ("started", "finished")
|
|
1608
|
+
if status not in valid_states:
|
|
1609
|
+
message = f"Invalid worker state. The worker state has to be in {valid_states}"
|
|
1610
|
+
tmp_log.debug(message)
|
|
1611
|
+
return message
|
|
1612
|
+
|
|
1613
|
+
accept_json = req.acceptJson()
|
|
1614
|
+
|
|
1615
|
+
return jobDispatcher.updateWorkerPilotStatus(workerID, harvesterID, status, timeout, accept_json, node_id)
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
# get max workerID
|
|
1619
|
+
def get_max_worker_id(req, harvester_id):
|
|
1620
|
+
return jobDispatcher.get_max_worker_id(harvester_id)
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
# get events status
|
|
1624
|
+
def get_events_status(req, ids):
|
|
1625
|
+
return jobDispatcher.get_events_status(ids)
|