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,1686 @@
|
|
|
1
|
+
"""
|
|
2
|
+
client methods
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import gzip
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import pickle
|
|
9
|
+
import socket
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
from pandacommon.pandautils.net_utils import replace_hostname_in_url_randomly
|
|
15
|
+
|
|
16
|
+
from pandaserver.api.v1.http_client import HttpClient as HttpClientV1
|
|
17
|
+
from pandaserver.api.v1.http_client import api_url_ssl as api_url_ssl_v1
|
|
18
|
+
from pandaserver.taskbuffer.JobUtils import dump_jobs_json
|
|
19
|
+
|
|
20
|
+
# PanDA server configuration
|
|
21
|
+
baseURL = os.environ.get("PANDA_URL", "http://pandaserver.cern.ch:25080/server/panda")
|
|
22
|
+
baseURLSSL = os.environ.get("PANDA_URL_SSL", "https://pandaserver.cern.ch:25443/server/panda")
|
|
23
|
+
|
|
24
|
+
DEFAULT_CERT_PATH = "/etc/grid-security/certificates"
|
|
25
|
+
|
|
26
|
+
# exit code
|
|
27
|
+
EC_Failed = 255
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_https(url):
|
|
31
|
+
# check if https is used
|
|
32
|
+
return url.startswith("https://")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def pickle_dumps(obj):
|
|
36
|
+
# wrapper for pickle with python 3
|
|
37
|
+
return pickle.dumps(obj, protocol=0)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def pickle_loads(obj_string):
|
|
41
|
+
try:
|
|
42
|
+
return pickle.loads(obj_string.encode())
|
|
43
|
+
except Exception:
|
|
44
|
+
return pickle.loads(obj_string)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HttpClient:
|
|
48
|
+
def __init__(self):
|
|
49
|
+
# verification of the host certificate
|
|
50
|
+
if "PANDA_VERIFY_HOST" in os.environ and os.environ["PANDA_VERIFY_HOST"] == "off":
|
|
51
|
+
self.verifyHost = False
|
|
52
|
+
else:
|
|
53
|
+
self.verifyHost = True
|
|
54
|
+
|
|
55
|
+
# request a compressed response
|
|
56
|
+
self.compress = True
|
|
57
|
+
|
|
58
|
+
# SSL cert/key
|
|
59
|
+
self.ssl_certificate = self._x509()
|
|
60
|
+
self.ssl_key = self._x509()
|
|
61
|
+
|
|
62
|
+
self.use_json = False
|
|
63
|
+
|
|
64
|
+
# OIDC
|
|
65
|
+
self.oidc = os.getenv("PANDA_AUTH") == "oidc"
|
|
66
|
+
self.auth_vo = os.getenv("PANDA_AUTH_VO") if self.oidc else None
|
|
67
|
+
self.id_token = os.getenv("PANDA_AUTH_ID_TOKEN") if self.oidc else None
|
|
68
|
+
if self.id_token and self.id_token.startswith("file:"):
|
|
69
|
+
with open(self.id_token[5:], "r") as f:
|
|
70
|
+
self.id_token = f.read().strip()
|
|
71
|
+
|
|
72
|
+
def _x509(self):
|
|
73
|
+
# retrieve the X509_USER_PROXY from the environment variables and check if it is readable
|
|
74
|
+
try:
|
|
75
|
+
if "X509_USER_PROXY" in os.environ and os.access(os.environ["X509_USER_PROXY"], os.R_OK):
|
|
76
|
+
return os.environ["X509_USER_PROXY"]
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
# look for the default place
|
|
81
|
+
x509 = f"/tmp/x509up_u{os.getuid()}"
|
|
82
|
+
if os.access(x509, os.R_OK):
|
|
83
|
+
return x509
|
|
84
|
+
|
|
85
|
+
# no valid proxy certificate
|
|
86
|
+
print("No valid grid proxy certificate found")
|
|
87
|
+
return ""
|
|
88
|
+
|
|
89
|
+
def _prepare_url(self, url):
|
|
90
|
+
"""Modify URL with HTTPS check and hostname replacement."""
|
|
91
|
+
use_https = is_https(url)
|
|
92
|
+
if "PANDA_BEHIND_REAL_LB" in os.environ:
|
|
93
|
+
modified_url = url
|
|
94
|
+
else:
|
|
95
|
+
modified_url = replace_hostname_in_url_randomly(url)
|
|
96
|
+
return modified_url, use_https
|
|
97
|
+
|
|
98
|
+
def _prepare_headers(self):
|
|
99
|
+
"""Prepare headers based on authentication and JSON settings."""
|
|
100
|
+
headers = {}
|
|
101
|
+
|
|
102
|
+
if self.oidc:
|
|
103
|
+
headers["Authorization"] = f"Bearer {self.id_token}"
|
|
104
|
+
headers["Origin"] = self.auth_vo
|
|
105
|
+
|
|
106
|
+
if self.use_json:
|
|
107
|
+
headers["Accept"] = "application/json"
|
|
108
|
+
|
|
109
|
+
return headers
|
|
110
|
+
|
|
111
|
+
def _prepare_ssl(self, use_https):
|
|
112
|
+
"""Prepare SSL configuration based on HTTPS usage and verification settings."""
|
|
113
|
+
cert = None # no certificate by default when no HTTS or using oidc headers
|
|
114
|
+
verify = True # validate against default system CA certificates
|
|
115
|
+
|
|
116
|
+
if use_https:
|
|
117
|
+
# oidc tokens are added to the headers, we don't need to provide a certificate
|
|
118
|
+
if not self.oidc:
|
|
119
|
+
cert = (self.ssl_certificate, self.ssl_key)
|
|
120
|
+
|
|
121
|
+
# the host verification has been disabled in the configuration
|
|
122
|
+
if not self.verifyHost:
|
|
123
|
+
verify = False
|
|
124
|
+
# there is a path to the CA certificate folder and it exists
|
|
125
|
+
elif "X509_CERT_DIR" in os.environ and os.path.exists(os.environ["X509_CERT_DIR"]):
|
|
126
|
+
verify = os.environ["X509_CERT_DIR"]
|
|
127
|
+
# the CA certificate folder is available in the standard location
|
|
128
|
+
elif os.path.exists(DEFAULT_CERT_PATH):
|
|
129
|
+
verify = DEFAULT_CERT_PATH
|
|
130
|
+
|
|
131
|
+
return cert, verify
|
|
132
|
+
|
|
133
|
+
def get(self, url, data):
|
|
134
|
+
url, use_https = self._prepare_url(url)
|
|
135
|
+
headers = self._prepare_headers()
|
|
136
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
response = requests.get(url, headers=headers, params=data, timeout=600, cert=cert, verify=verify)
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
return 0, response.text
|
|
142
|
+
except requests.RequestException as e:
|
|
143
|
+
return 255, str(e)
|
|
144
|
+
|
|
145
|
+
def post(self, url, data):
|
|
146
|
+
url, use_https = self._prepare_url(url)
|
|
147
|
+
headers = self._prepare_headers()
|
|
148
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
response = requests.post(url, headers=headers, data=data, timeout=600, cert=cert, verify=verify)
|
|
152
|
+
response.raise_for_status()
|
|
153
|
+
return 0, response.text
|
|
154
|
+
except requests.RequestException as e:
|
|
155
|
+
return 255, str(e)
|
|
156
|
+
|
|
157
|
+
def post_files(self, url, data):
|
|
158
|
+
url, use_https = self._prepare_url(url)
|
|
159
|
+
headers = self._prepare_headers()
|
|
160
|
+
cert, verify = self._prepare_ssl(use_https)
|
|
161
|
+
|
|
162
|
+
files = {}
|
|
163
|
+
try:
|
|
164
|
+
for key, value in data.items():
|
|
165
|
+
if type(data[key]) == str:
|
|
166
|
+
# we got a file to upload without specifying the destination name
|
|
167
|
+
files[key] = open(data[key], "rb")
|
|
168
|
+
else:
|
|
169
|
+
# we got a file to upload which specifies the destination name
|
|
170
|
+
files[key] = (data[key][0], open(data[key][1], "rb"))
|
|
171
|
+
response = requests.post(url, headers=headers, files=files, timeout=600, cert=cert, verify=verify)
|
|
172
|
+
response.raise_for_status()
|
|
173
|
+
return 0, response.text
|
|
174
|
+
except requests.RequestException as e:
|
|
175
|
+
return 255, str(e)
|
|
176
|
+
finally:
|
|
177
|
+
for file in files.values():
|
|
178
|
+
if type(file) == tuple:
|
|
179
|
+
file_handler = file[1]
|
|
180
|
+
else:
|
|
181
|
+
file_handler = file
|
|
182
|
+
file_handler.close()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
Client API
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def submit_jobs(jobs):
|
|
191
|
+
"""
|
|
192
|
+
Submit jobs
|
|
193
|
+
|
|
194
|
+
args:
|
|
195
|
+
jobs: the list of JobSpecs
|
|
196
|
+
returns:
|
|
197
|
+
status code
|
|
198
|
+
0: communication succeeded to the panda server
|
|
199
|
+
255: communication failure
|
|
200
|
+
return code
|
|
201
|
+
True: request is processed
|
|
202
|
+
False: not processed
|
|
203
|
+
"""
|
|
204
|
+
# set hostname to jobs
|
|
205
|
+
hostname = socket.getfqdn()
|
|
206
|
+
for job in jobs:
|
|
207
|
+
job.creationHost = hostname
|
|
208
|
+
|
|
209
|
+
# serialize the jobs to json
|
|
210
|
+
jobs = dump_jobs_json(jobs)
|
|
211
|
+
|
|
212
|
+
http_client = HttpClientV1()
|
|
213
|
+
|
|
214
|
+
url = f"{api_url_ssl_v1}/job/submit"
|
|
215
|
+
data = {"jobs": jobs}
|
|
216
|
+
|
|
217
|
+
status, output = http_client.post(url, data)
|
|
218
|
+
return status, output
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_job_status(job_ids):
|
|
222
|
+
"""
|
|
223
|
+
Get job status
|
|
224
|
+
|
|
225
|
+
args:
|
|
226
|
+
job_ids: the list of PandaIDs
|
|
227
|
+
returns:
|
|
228
|
+
status code
|
|
229
|
+
0: communication succeeded to the panda server
|
|
230
|
+
255: communication failure
|
|
231
|
+
the list of JobSpecs (or Nones for non-existing PandaIDs)
|
|
232
|
+
"""
|
|
233
|
+
http_client = HttpClientV1()
|
|
234
|
+
url = f"{api_url_ssl_v1}/pilot/get_job_status"
|
|
235
|
+
data = {"job_ids": job_ids}
|
|
236
|
+
status, output = http_client.post(url, data)
|
|
237
|
+
|
|
238
|
+
return status, output
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def kill_jobs(
|
|
242
|
+
job_ids,
|
|
243
|
+
code=None,
|
|
244
|
+
keep_unmerged=False,
|
|
245
|
+
job_sub_status=None,
|
|
246
|
+
):
|
|
247
|
+
"""
|
|
248
|
+
Kill jobs. Normal users can kill only their own jobs.
|
|
249
|
+
People with production VOMS role can kill any jobs.
|
|
250
|
+
Running jobs are killed when next heartbeat comes from the pilot.
|
|
251
|
+
Set code=9 if running jobs need to be killed immediately.
|
|
252
|
+
|
|
253
|
+
args:
|
|
254
|
+
job_ids: the list of PandaIDs
|
|
255
|
+
code: specify why the jobs are killed
|
|
256
|
+
2: expire
|
|
257
|
+
3: aborted
|
|
258
|
+
4: expire in waiting
|
|
259
|
+
7: retry by server
|
|
260
|
+
8: re-brokerage
|
|
261
|
+
9: force kill
|
|
262
|
+
10: fast re-brokerage on overloaded PQs
|
|
263
|
+
50: kill by JEDI
|
|
264
|
+
91: kill user jobs with prod role
|
|
265
|
+
keep_unmerged: set True not to cancel unmerged jobs when pmerge is killed.
|
|
266
|
+
job_sub_status: set job sub status if any
|
|
267
|
+
returns:
|
|
268
|
+
status code
|
|
269
|
+
0: communication succeeded to the panda server
|
|
270
|
+
255: communication failure
|
|
271
|
+
the list of clouds (or Nones if tasks are not yet assigned)
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
http_client = HttpClientV1()
|
|
275
|
+
|
|
276
|
+
url = f"{api_url_ssl_v1}/job/kill"
|
|
277
|
+
data = {"job_ids": job_ids}
|
|
278
|
+
|
|
279
|
+
if code:
|
|
280
|
+
data["code"] = code
|
|
281
|
+
|
|
282
|
+
kill_options = []
|
|
283
|
+
if keep_unmerged == True:
|
|
284
|
+
kill_options.append("keepUnmerged")
|
|
285
|
+
if job_sub_status:
|
|
286
|
+
kill_options.append(f"jobSubStatus={job_sub_status}")
|
|
287
|
+
if kill_options:
|
|
288
|
+
data["kill_options"] = kill_options
|
|
289
|
+
|
|
290
|
+
status, output = http_client.post(url, data)
|
|
291
|
+
return status, output
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def reassign_jobs(job_ids):
|
|
295
|
+
"""
|
|
296
|
+
Triggers reassignment of jobs.
|
|
297
|
+
|
|
298
|
+
args:
|
|
299
|
+
ids: the list of taskIDs
|
|
300
|
+
returns:
|
|
301
|
+
status code
|
|
302
|
+
0: communication succeeded to the panda server
|
|
303
|
+
255: communication failure
|
|
304
|
+
return code
|
|
305
|
+
True: request is processed
|
|
306
|
+
False: not processed
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
http_client = HttpClientV1()
|
|
310
|
+
|
|
311
|
+
url = f"{api_url_ssl_v1}/job/reassign"
|
|
312
|
+
data = {"job_ids": job_ids}
|
|
313
|
+
status, output = http_client.post(url, data)
|
|
314
|
+
return status, output
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def job_stats_by_cloud(job_type=None):
|
|
318
|
+
"""
|
|
319
|
+
Get job statistics by cloud. Used by panglia monitor in TRIUMF
|
|
320
|
+
|
|
321
|
+
args:
|
|
322
|
+
job_type: string with the type of jobs to consider
|
|
323
|
+
analysis: analysis jobs
|
|
324
|
+
production: production jobs
|
|
325
|
+
returns:
|
|
326
|
+
status code
|
|
327
|
+
0: communication succeeded to the panda server
|
|
328
|
+
255: communication failure
|
|
329
|
+
map of the number jobs per job status in each site
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
if job_type not in [None, "analysis", "production"]:
|
|
333
|
+
print("Invalid job type, must be one of [None, 'analysis', 'production'']")
|
|
334
|
+
return EC_Failed, "Invalid job type"
|
|
335
|
+
|
|
336
|
+
http_client = HttpClientV1()
|
|
337
|
+
url = f"{api_url_ssl_v1}/statistics/job_stats_by_cloud"
|
|
338
|
+
|
|
339
|
+
data = {}
|
|
340
|
+
if job_type:
|
|
341
|
+
data["type"] = job_type
|
|
342
|
+
|
|
343
|
+
status, output = http_client.get(url, data)
|
|
344
|
+
if status != 0 or not output.get("success"):
|
|
345
|
+
print(f"Failed to retrieve job_stats_by_cloud for {job_type}. Status: {status}, Output: {output}")
|
|
346
|
+
return status, output
|
|
347
|
+
|
|
348
|
+
statistics = output["data"]
|
|
349
|
+
ret = {}
|
|
350
|
+
for cloud, values in statistics.items():
|
|
351
|
+
if cloud not in ret:
|
|
352
|
+
# append cloud values (make a shallow copy to avoid mutating original)
|
|
353
|
+
ret[cloud] = dict(values)
|
|
354
|
+
else:
|
|
355
|
+
# sum statistics per status
|
|
356
|
+
for job_status, count in values.items():
|
|
357
|
+
ret[cloud][job_status] = ret[cloud].get(job_status, 0) + count
|
|
358
|
+
|
|
359
|
+
return 0, ret
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# alias the old name to the new function for backwards compatibility
|
|
363
|
+
def getJobStatistics(sourcetype=None):
|
|
364
|
+
return job_stats_by_cloud(sourcetype)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def production_job_stats_by_cloud_and_processing_type():
|
|
368
|
+
"""
|
|
369
|
+
Get job statistics by cloud and processing type. Used by panglia monitor in TRIUMF
|
|
370
|
+
|
|
371
|
+
returns:
|
|
372
|
+
status code
|
|
373
|
+
0: communication succeeded to the panda server
|
|
374
|
+
255: communication failure
|
|
375
|
+
map of the number jobs per job status in each site
|
|
376
|
+
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
http_client = HttpClientV1()
|
|
380
|
+
url = f"{api_url_ssl_v1}/statistics/production_job_stats_by_cloud_and_processing_type"
|
|
381
|
+
status, output = http_client.get(url, {})
|
|
382
|
+
|
|
383
|
+
if status != 0 or not output.get("success"):
|
|
384
|
+
print(f"Failed to retrieve production_job_stats_by_cloud_and_processing_type. Status: {status}, Output: {output}")
|
|
385
|
+
return status, output
|
|
386
|
+
|
|
387
|
+
statistics = output["data"]
|
|
388
|
+
aggregated = {}
|
|
389
|
+
|
|
390
|
+
for cloud, processing_type_map in statistics.items():
|
|
391
|
+
# ensure we work with a shallow copy of the incoming mappings
|
|
392
|
+
processing_type_map = dict(processing_type_map)
|
|
393
|
+
|
|
394
|
+
if cloud not in aggregated:
|
|
395
|
+
# copy nested structures to avoid mutating the original
|
|
396
|
+
aggregated[cloud] = {processing_type: dict(status_map) for processing_type, status_map in processing_type_map.items()}
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
# merge into existing cloud entry
|
|
400
|
+
for processing_type, status_map in processing_type_map.items():
|
|
401
|
+
status_map = dict(status_map)
|
|
402
|
+
if processing_type not in aggregated[cloud]:
|
|
403
|
+
aggregated[cloud][processing_type] = status_map
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
# sum counts per status
|
|
407
|
+
for status, count in status_map.items():
|
|
408
|
+
aggregated[cloud][processing_type][status] = aggregated[cloud][processing_type].get(status, 0) + count
|
|
409
|
+
return 0, aggregated
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# alias the old name to the new function for backwards compatibility
|
|
413
|
+
def getJobStatisticsForBamboo(useMorePG=False):
|
|
414
|
+
return production_job_stats_by_cloud_and_processing_type()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def job_stats_by_site_and_resource_type(time_window=None):
|
|
418
|
+
"""
|
|
419
|
+
Get job statistics per site and resource. Used by panglia monitor in TRIUMF
|
|
420
|
+
|
|
421
|
+
args:
|
|
422
|
+
time_window: to count number of jobs that finish/failed/cancelled for last N minutes. 12*60 by default
|
|
423
|
+
returns:
|
|
424
|
+
status code
|
|
425
|
+
0: communication succeeded to the panda server
|
|
426
|
+
255: communication failure
|
|
427
|
+
map of the number jobs per job status in each site and resource
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
http_client = HttpClientV1()
|
|
432
|
+
url = f"{api_url_ssl_v1}/statistics/job_stats_by_site_and_resource_type"
|
|
433
|
+
data = {}
|
|
434
|
+
if time_window:
|
|
435
|
+
data["time_window"] = time_window
|
|
436
|
+
|
|
437
|
+
status, output = http_client.get(url, data)
|
|
438
|
+
|
|
439
|
+
if status != 0 or not output.get("success"):
|
|
440
|
+
print(f"Failed to retrieve job_stats_by_site_and_resource_type. Status: {status}, Output: {output}")
|
|
441
|
+
return status, output
|
|
442
|
+
|
|
443
|
+
return 0, output.get("data")
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# alias the old name to the new function for backwards compatibility
|
|
447
|
+
def getJobStatisticsPerSiteResource(timeWindow=None):
|
|
448
|
+
return job_stats_by_site_and_resource_type(timeWindow)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def job_stats_by_site_share_and_resource_type(time_window=None):
|
|
452
|
+
"""
|
|
453
|
+
Get job statistics per site, label, and resource
|
|
454
|
+
|
|
455
|
+
args:
|
|
456
|
+
timeWindow: to count number of jobs that finish/failed/cancelled for last N minutes. 12*60 by default
|
|
457
|
+
returns:
|
|
458
|
+
status code
|
|
459
|
+
0: communication succeeded to the panda server
|
|
460
|
+
255: communication failure
|
|
461
|
+
map of the number jobs per job status in each site and resource
|
|
462
|
+
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
http_client = HttpClientV1()
|
|
466
|
+
url = f"{api_url_ssl_v1}/statistics/job_stats_by_site_share_and_resource_type"
|
|
467
|
+
data = {}
|
|
468
|
+
if time_window:
|
|
469
|
+
data["time_window"] = time_window
|
|
470
|
+
|
|
471
|
+
status, output = http_client.get(url, data)
|
|
472
|
+
|
|
473
|
+
if status != 0 or not output.get("success"):
|
|
474
|
+
print(f"Failed to retrieve job_stats_by_site_share_and_resource_type. Status: {status}, Output: {output}")
|
|
475
|
+
return status, output
|
|
476
|
+
|
|
477
|
+
return 0, output.get("data")
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# alias the old name to the new function for backwards compatibility
|
|
481
|
+
def get_job_statistics_per_site_label_resource(time_window=None):
|
|
482
|
+
return job_stats_by_site_share_and_resource_type(time_window)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def get_site_specs(site_type=None):
|
|
486
|
+
"""
|
|
487
|
+
Get list of site specifications. Used by panglia monitor in TRIUMF
|
|
488
|
+
|
|
489
|
+
args:
|
|
490
|
+
site_type: type of sites
|
|
491
|
+
all: all sites
|
|
492
|
+
None: defaults to analysis sites
|
|
493
|
+
analysis: analysis sites
|
|
494
|
+
production: production sites
|
|
495
|
+
unified: unified sites
|
|
496
|
+
returns:
|
|
497
|
+
status code
|
|
498
|
+
0: communication succeeded to the panda server
|
|
499
|
+
255: communication failure
|
|
500
|
+
map of site and attributes
|
|
501
|
+
|
|
502
|
+
"""
|
|
503
|
+
http_client = HttpClientV1()
|
|
504
|
+
url = f"{api_url_ssl_v1}/metaconfig/get_site_specs"
|
|
505
|
+
if site_type not in [None, "all", "analysis", "production", "unified"]:
|
|
506
|
+
return EC_Failed, "Invalid site type"
|
|
507
|
+
|
|
508
|
+
data = {}
|
|
509
|
+
if site_type:
|
|
510
|
+
data = {"type": site_type}
|
|
511
|
+
|
|
512
|
+
status, output = http_client.get(url, data)
|
|
513
|
+
|
|
514
|
+
if status != 0 or not output.get("success"):
|
|
515
|
+
print(f"Failed to retrieve get_site_specs. Status: {status}, Output: {output}")
|
|
516
|
+
return status, output
|
|
517
|
+
|
|
518
|
+
return 0, output.get("data")
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
# alias the old name to the new function for backwards compatibility
|
|
522
|
+
def getSiteSpecs(siteType=None):
|
|
523
|
+
return get_site_specs(siteType)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def register_cache_file(user_name: str, file_name: str, file_size: int, checksum: str):
|
|
527
|
+
"""
|
|
528
|
+
Register information about the input sandbox that is being stored in PanDA cache
|
|
529
|
+
|
|
530
|
+
args:
|
|
531
|
+
user_name: the name of the user
|
|
532
|
+
file_name: the file name
|
|
533
|
+
file_size: the file size
|
|
534
|
+
checksum: md5sum of the file
|
|
535
|
+
returns:
|
|
536
|
+
status code
|
|
537
|
+
0: communication succeeded to the panda server
|
|
538
|
+
else: communication failure
|
|
539
|
+
|
|
540
|
+
"""
|
|
541
|
+
http_client = HttpClientV1()
|
|
542
|
+
|
|
543
|
+
url = f"{api_url_ssl_v1}/file_server/register_cache_file"
|
|
544
|
+
|
|
545
|
+
data = {
|
|
546
|
+
"user_name": user_name,
|
|
547
|
+
"file_name": file_name,
|
|
548
|
+
"file_size": file_size,
|
|
549
|
+
"checksum": str(checksum),
|
|
550
|
+
}
|
|
551
|
+
return http_client.post(url, data)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def put_file(file):
|
|
555
|
+
"""
|
|
556
|
+
Upload input sandbox to PanDA cache
|
|
557
|
+
|
|
558
|
+
args:
|
|
559
|
+
file: the file name
|
|
560
|
+
returns:
|
|
561
|
+
status code
|
|
562
|
+
0: communication succeeded to the panda server
|
|
563
|
+
else: communication failure
|
|
564
|
+
|
|
565
|
+
"""
|
|
566
|
+
|
|
567
|
+
http_client = HttpClientV1()
|
|
568
|
+
url = f"{api_url_ssl_v1}/file_server/upload_cache_file"
|
|
569
|
+
data = {"file": file}
|
|
570
|
+
return http_client.post_files(url, data)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def touch_file(source_url, file_name):
|
|
574
|
+
http_client = HttpClientV1()
|
|
575
|
+
# Note the special construction of the URL here, since it is not going through the api_url_ssl_v1,
|
|
576
|
+
# but directly to the source_url pointing at the concrete instance provided
|
|
577
|
+
url = f"{source_url}/api/v1/file_server/touch_cache_file"
|
|
578
|
+
data = {"file_name": file_name}
|
|
579
|
+
return http_client.post(url, data)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def insertTaskParams(taskParams):
|
|
583
|
+
"""
|
|
584
|
+
Insert task parameters
|
|
585
|
+
|
|
586
|
+
args:
|
|
587
|
+
taskParams: a dictionary of task parameters
|
|
588
|
+
returns:
|
|
589
|
+
status code
|
|
590
|
+
0: communication succeeded to the panda server
|
|
591
|
+
255: communication failure
|
|
592
|
+
tuple of return code and JediTaskID
|
|
593
|
+
True: request is processed
|
|
594
|
+
False: not processed
|
|
595
|
+
"""
|
|
596
|
+
# serialize
|
|
597
|
+
taskParamsStr = json.dumps(taskParams)
|
|
598
|
+
|
|
599
|
+
http_client = HttpClient()
|
|
600
|
+
|
|
601
|
+
# execute
|
|
602
|
+
url = f"{baseURLSSL}/insertTaskParams"
|
|
603
|
+
data = {"taskParams": taskParamsStr}
|
|
604
|
+
status, output = http_client.post(url, data)
|
|
605
|
+
try:
|
|
606
|
+
return status, pickle_loads(output)
|
|
607
|
+
except Exception:
|
|
608
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
609
|
+
error_str = f"ERROR insertTaskParams : {error_type} {error_value}"
|
|
610
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def kill_task(task_id, broadcast=False):
|
|
614
|
+
"""
|
|
615
|
+
Kill a task
|
|
616
|
+
|
|
617
|
+
args:
|
|
618
|
+
task_id: task ID of the task to be killed
|
|
619
|
+
broadcast: True to push the message to the pilot subscribing the MB
|
|
620
|
+
returns:
|
|
621
|
+
status code
|
|
622
|
+
0: communication succeeded to the panda server
|
|
623
|
+
255: communication failure
|
|
624
|
+
tuple of return code and diagnostic message
|
|
625
|
+
0: request is registered
|
|
626
|
+
1: server error
|
|
627
|
+
2: task not found
|
|
628
|
+
3: permission denied
|
|
629
|
+
4: irrelevant task status
|
|
630
|
+
100: non SSL connection
|
|
631
|
+
101: irrelevant taskID
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
http_client = HttpClientV1()
|
|
635
|
+
|
|
636
|
+
url = f"{api_url_ssl_v1}/task/kill"
|
|
637
|
+
data = {"task_id": task_id, "broadcast": broadcast}
|
|
638
|
+
|
|
639
|
+
status, output = http_client.post(url, data)
|
|
640
|
+
|
|
641
|
+
return status, output
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def finish_task(task_id, soft=False, broadcast=False):
|
|
645
|
+
"""
|
|
646
|
+
Finish a task
|
|
647
|
+
|
|
648
|
+
args:
|
|
649
|
+
jediTaskID: jediTaskID of the task to be finished
|
|
650
|
+
soft: If True, new jobs are not generated and the task is
|
|
651
|
+
finished once all remaining jobs are done.
|
|
652
|
+
If False, all remaining jobs are killed and then the
|
|
653
|
+
task is finished
|
|
654
|
+
broadcast: True to push the message to the pilot subscribing the MB
|
|
655
|
+
returns:
|
|
656
|
+
status code
|
|
657
|
+
0: communication succeeded to the panda server
|
|
658
|
+
255: communication failure
|
|
659
|
+
tuple of return code and diagnostic message
|
|
660
|
+
0: request is registered
|
|
661
|
+
1: server error
|
|
662
|
+
2: task not found
|
|
663
|
+
3: permission denied
|
|
664
|
+
4: irrelevant task status
|
|
665
|
+
100: non SSL connection
|
|
666
|
+
101: irrelevant taskID
|
|
667
|
+
"""
|
|
668
|
+
http_client = HttpClientV1()
|
|
669
|
+
|
|
670
|
+
url = f"{api_url_ssl_v1}/task/finish"
|
|
671
|
+
data = {"task_id": task_id, "soft": soft, "broadcast": broadcast}
|
|
672
|
+
|
|
673
|
+
status, output = http_client.post(url, data)
|
|
674
|
+
|
|
675
|
+
return status, output
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def reassignTaskToSite(jediTaskID, site, mode=None):
|
|
679
|
+
"""
|
|
680
|
+
Reassign a task to a site. Existing jobs are killed and new jobs are generated at the site
|
|
681
|
+
|
|
682
|
+
args:
|
|
683
|
+
jediTaskID: jediTaskID of the task to be reassigned
|
|
684
|
+
site: the site name where the task is reassigned
|
|
685
|
+
mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
|
|
686
|
+
returns:
|
|
687
|
+
status code
|
|
688
|
+
0: communication succeeded to the panda server
|
|
689
|
+
255: communication failure
|
|
690
|
+
tuple of return code and diagnostic message
|
|
691
|
+
0: request is registered
|
|
692
|
+
1: server error
|
|
693
|
+
2: task not found
|
|
694
|
+
3: permission denied
|
|
695
|
+
4: irrelevant task status
|
|
696
|
+
100: non SSL connection
|
|
697
|
+
101: irrelevant taskID
|
|
698
|
+
"""
|
|
699
|
+
maxSite = 60
|
|
700
|
+
if site is not None and len(site) > maxSite:
|
|
701
|
+
return EC_Failed, f"site parameter is too long > {maxSite}chars"
|
|
702
|
+
|
|
703
|
+
http_client = HttpClient()
|
|
704
|
+
|
|
705
|
+
# execute
|
|
706
|
+
url = f"{baseURLSSL}/reassignTask"
|
|
707
|
+
data = {"jediTaskID": jediTaskID, "site": site}
|
|
708
|
+
if mode is not None:
|
|
709
|
+
data["mode"] = mode
|
|
710
|
+
status, output = http_client.post(url, data)
|
|
711
|
+
try:
|
|
712
|
+
return status, pickle_loads(output)
|
|
713
|
+
except Exception:
|
|
714
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
715
|
+
error_str = f"ERROR reassignTaskToSite : {error_type} {error_value}"
|
|
716
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def reassignTaskToCloud(jediTaskID, cloud, mode=None):
|
|
720
|
+
"""
|
|
721
|
+
Reassign a task to a cloud. Existing jobs are killed and new jobs are generated in the cloud
|
|
722
|
+
|
|
723
|
+
args:
|
|
724
|
+
jediTaskID: jediTaskID of the task to be reassigned
|
|
725
|
+
cloud: the cloud name where the task is reassigned
|
|
726
|
+
mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
|
|
727
|
+
returns:
|
|
728
|
+
status code
|
|
729
|
+
0: communication succeeded to the panda server
|
|
730
|
+
255: communication failure
|
|
731
|
+
tuple of return code and diagnostic message
|
|
732
|
+
0: request is registered
|
|
733
|
+
1: server error
|
|
734
|
+
2: task not found
|
|
735
|
+
3: permission denied
|
|
736
|
+
4: irrelevant task status
|
|
737
|
+
100: non SSL connection
|
|
738
|
+
101: irrelevant taskID
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
http_client = HttpClient()
|
|
742
|
+
|
|
743
|
+
# execute
|
|
744
|
+
url = f"{baseURLSSL}/reassignTask"
|
|
745
|
+
data = {"jediTaskID": jediTaskID, "cloud": cloud}
|
|
746
|
+
if mode is not None:
|
|
747
|
+
data["mode"] = mode
|
|
748
|
+
status, output = http_client.post(url, data)
|
|
749
|
+
try:
|
|
750
|
+
return status, pickle_loads(output)
|
|
751
|
+
except Exception:
|
|
752
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
753
|
+
error_str = f"ERROR reassignTaskToCloud : {error_type} {error_value}"
|
|
754
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def reassignTaskToNucleus(jediTaskID, nucleus, mode=None):
|
|
758
|
+
"""
|
|
759
|
+
Reassign a task to a nucleus. Existing jobs are killed and new jobs are generated in the cloud
|
|
760
|
+
|
|
761
|
+
args:
|
|
762
|
+
jediTaskID: jediTaskID of the task to be reassigned
|
|
763
|
+
nucleus: the nucleus name where the task is reassigned
|
|
764
|
+
mode: If soft, only defined/waiting/assigned/activated jobs are killed. If nokill, no jobs are killed. All jobs are killed by default.
|
|
765
|
+
returns:
|
|
766
|
+
status code
|
|
767
|
+
0: communication succeeded to the panda server
|
|
768
|
+
255: communication failure
|
|
769
|
+
tuple of return code and diagnostic message
|
|
770
|
+
0: request is registered
|
|
771
|
+
1: server error
|
|
772
|
+
2: task not found
|
|
773
|
+
3: permission denied
|
|
774
|
+
4: irrelevant task status
|
|
775
|
+
100: non SSL connection
|
|
776
|
+
101: irrelevant taskID
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
http_client = HttpClient()
|
|
780
|
+
|
|
781
|
+
# execute
|
|
782
|
+
url = f"{baseURLSSL}/reassignTask"
|
|
783
|
+
data = {"jediTaskID": jediTaskID, "nucleus": nucleus}
|
|
784
|
+
if mode is not None:
|
|
785
|
+
data["mode"] = mode
|
|
786
|
+
status, output = http_client.post(url, data)
|
|
787
|
+
try:
|
|
788
|
+
return status, pickle_loads(output)
|
|
789
|
+
except Exception:
|
|
790
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
791
|
+
error_str = f"ERROR reassignTaskToCloud : {error_type} {error_value}"
|
|
792
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def uploadLog(log_string, log_file_name):
|
|
796
|
+
"""
|
|
797
|
+
Upload log
|
|
798
|
+
|
|
799
|
+
args:
|
|
800
|
+
logStr: log message
|
|
801
|
+
log_file_name: name of log file
|
|
802
|
+
returns:
|
|
803
|
+
status code
|
|
804
|
+
0: communication succeeded to the panda server
|
|
805
|
+
else: communication failure
|
|
806
|
+
|
|
807
|
+
"""
|
|
808
|
+
|
|
809
|
+
http_client = HttpClientV1()
|
|
810
|
+
|
|
811
|
+
# write log to a tmp file
|
|
812
|
+
fh = tempfile.NamedTemporaryFile(delete=False)
|
|
813
|
+
gfh = gzip.open(fh.name, mode="wb")
|
|
814
|
+
if sys.version_info[0] >= 3:
|
|
815
|
+
log_string = log_string.encode("utf-8")
|
|
816
|
+
gfh.write(log_string)
|
|
817
|
+
gfh.close()
|
|
818
|
+
# execute
|
|
819
|
+
url = f"{api_url_ssl_v1}/file_server/upload_jedi_log"
|
|
820
|
+
|
|
821
|
+
# sometimes the destination file name (=logFileName) comes as an integer (e.g. a JEDI task ID) and it needs to be converted to a string
|
|
822
|
+
log_file_name = str(log_file_name)
|
|
823
|
+
data = {"file": (log_file_name, fh.name)}
|
|
824
|
+
return_value = http_client.post_files(url, data, encoding="gzip")
|
|
825
|
+
os.unlink(fh.name)
|
|
826
|
+
return return_value
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def changeTaskPriority(jediTaskID, newPriority):
|
|
830
|
+
"""
|
|
831
|
+
Change the task priority
|
|
832
|
+
|
|
833
|
+
args:
|
|
834
|
+
jediTaskID: jediTaskID of the task to change the priority
|
|
835
|
+
newPriority: new task priority
|
|
836
|
+
returns:
|
|
837
|
+
status code
|
|
838
|
+
0: communication succeeded to the panda server
|
|
839
|
+
255: communication failure
|
|
840
|
+
return code
|
|
841
|
+
0: unknown task
|
|
842
|
+
1: succeeded
|
|
843
|
+
None: database error
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
http_client = HttpClient()
|
|
847
|
+
|
|
848
|
+
# execute
|
|
849
|
+
url = f"{baseURLSSL}/changeTaskPriority"
|
|
850
|
+
data = {"jediTaskID": jediTaskID, "newPriority": newPriority}
|
|
851
|
+
status, output = http_client.post(url, data)
|
|
852
|
+
try:
|
|
853
|
+
return status, pickle_loads(output)
|
|
854
|
+
except Exception:
|
|
855
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
856
|
+
error_str = f"ERROR changeTaskPriority : {error_type} {error_value}"
|
|
857
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def set_debug_mode(job_id, mode):
|
|
861
|
+
"""
|
|
862
|
+
Turn debug mode for a job on/off
|
|
863
|
+
|
|
864
|
+
args:
|
|
865
|
+
job_id: job_id of the job
|
|
866
|
+
mode: True to turn it on. Oppositely, False
|
|
867
|
+
returns:
|
|
868
|
+
status code
|
|
869
|
+
0: communication succeeded to the panda server
|
|
870
|
+
another: communication failure
|
|
871
|
+
error message
|
|
872
|
+
"""
|
|
873
|
+
|
|
874
|
+
http_client = HttpClientV1()
|
|
875
|
+
|
|
876
|
+
url = f"{api_url_ssl_v1}/job/set_debug_mode"
|
|
877
|
+
data = {"job_id": job_id, "mode": mode}
|
|
878
|
+
|
|
879
|
+
status, output = http_client.post(url, data)
|
|
880
|
+
|
|
881
|
+
return status, output
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def retry_task(task_id, no_child_retry=False, discard_events=False, disable_staging_mode=False, keep_gshare_priority=False):
|
|
885
|
+
"""
|
|
886
|
+
Retry a task
|
|
887
|
+
|
|
888
|
+
args:
|
|
889
|
+
task_id: jediTaskID of the task to retry
|
|
890
|
+
no_child_retry: True not to retry child tasks
|
|
891
|
+
discard_events: discard events
|
|
892
|
+
disable_staging_mode: disable staging mode
|
|
893
|
+
keep_gshare_priority: keep current gshare and priority
|
|
894
|
+
returns:
|
|
895
|
+
status code
|
|
896
|
+
0: communication succeeded to the panda server
|
|
897
|
+
255: communication failure
|
|
898
|
+
tuple of return code and diagnostic message
|
|
899
|
+
0: request is registered
|
|
900
|
+
1: server error
|
|
901
|
+
2: task not found
|
|
902
|
+
3: permission denied
|
|
903
|
+
4: irrelevant task status
|
|
904
|
+
100: non SSL connection
|
|
905
|
+
101: irrelevant taskID
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
http_client = HttpClientV1()
|
|
909
|
+
url = f"{api_url_ssl_v1}/task/retry"
|
|
910
|
+
|
|
911
|
+
data = {"task_id": task_id}
|
|
912
|
+
if no_child_retry:
|
|
913
|
+
data["no_child_retry"] = True
|
|
914
|
+
if discard_events:
|
|
915
|
+
data["discard_events"] = True
|
|
916
|
+
if disable_staging_mode:
|
|
917
|
+
data["disable_staging_mode"] = True
|
|
918
|
+
if keep_gshare_priority:
|
|
919
|
+
data["keep_gshare_priority"] = True
|
|
920
|
+
|
|
921
|
+
status, output = http_client.post(url, data)
|
|
922
|
+
return status, output
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def reload_input(task_id):
|
|
926
|
+
"""
|
|
927
|
+
Reload the input for a task
|
|
928
|
+
|
|
929
|
+
args:
|
|
930
|
+
jediTaskID: jediTaskID of the task to retry
|
|
931
|
+
returns:
|
|
932
|
+
status code
|
|
933
|
+
0: communication succeeded to the panda server
|
|
934
|
+
255: communication failure
|
|
935
|
+
tuple of return code and diagnostic message
|
|
936
|
+
0: request is registered
|
|
937
|
+
1: server error
|
|
938
|
+
2: task not found
|
|
939
|
+
3: permission denied
|
|
940
|
+
4: irrelevant task status
|
|
941
|
+
100: non SSL connection
|
|
942
|
+
101: irrelevant taskID
|
|
943
|
+
"""
|
|
944
|
+
http_client = HttpClientV1()
|
|
945
|
+
|
|
946
|
+
url = f"{api_url_ssl_v1}/task/reload_input"
|
|
947
|
+
data = {"task_id": task_id}
|
|
948
|
+
|
|
949
|
+
status, output = http_client.post(url, data)
|
|
950
|
+
|
|
951
|
+
return status, output
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def changeTaskWalltime(jediTaskID, wallTime):
|
|
955
|
+
"""
|
|
956
|
+
Change task walltime
|
|
957
|
+
|
|
958
|
+
args:
|
|
959
|
+
jediTaskID: jediTaskID of the task to change the priority
|
|
960
|
+
wallTime: new walltime for the task
|
|
961
|
+
returns:
|
|
962
|
+
status code
|
|
963
|
+
0: communication succeeded to the panda server
|
|
964
|
+
255: communication failure
|
|
965
|
+
return code
|
|
966
|
+
0: unknown task
|
|
967
|
+
1: succeeded
|
|
968
|
+
None: database error
|
|
969
|
+
"""
|
|
970
|
+
|
|
971
|
+
http_client = HttpClient()
|
|
972
|
+
|
|
973
|
+
# execute
|
|
974
|
+
url = f"{baseURLSSL}/changeTaskAttributePanda"
|
|
975
|
+
data = {"jediTaskID": jediTaskID, "attrName": "wallTime", "attrValue": wallTime}
|
|
976
|
+
status, output = http_client.post(url, data)
|
|
977
|
+
try:
|
|
978
|
+
return status, pickle_loads(output)
|
|
979
|
+
except Exception:
|
|
980
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
981
|
+
error_str = f"ERROR changeTaskWalltime : {error_type} {error_value}"
|
|
982
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
def changeTaskCputime(jediTaskID, cpuTime):
|
|
986
|
+
"""
|
|
987
|
+
Change task CPU time
|
|
988
|
+
|
|
989
|
+
args:
|
|
990
|
+
jediTaskID: jediTaskID of the task to change the priority
|
|
991
|
+
cpuTime: new cputime for the task
|
|
992
|
+
returns:
|
|
993
|
+
status code
|
|
994
|
+
0: communication succeeded to the panda server
|
|
995
|
+
255: communication failure
|
|
996
|
+
return code
|
|
997
|
+
0: unknown task
|
|
998
|
+
1: succeeded
|
|
999
|
+
None: database error
|
|
1000
|
+
"""
|
|
1001
|
+
|
|
1002
|
+
http_client = HttpClient()
|
|
1003
|
+
|
|
1004
|
+
# execute
|
|
1005
|
+
url = f"{baseURLSSL}/changeTaskAttributePanda"
|
|
1006
|
+
data = {"jediTaskID": jediTaskID, "attrName": "cpuTime", "attrValue": cpuTime}
|
|
1007
|
+
status, output = http_client.post(url, data)
|
|
1008
|
+
try:
|
|
1009
|
+
return status, pickle_loads(output)
|
|
1010
|
+
except Exception:
|
|
1011
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1012
|
+
error_str = f"ERROR changeTaskCputime : {error_type} {error_value}"
|
|
1013
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def changeTaskRamCount(jediTaskID, ramCount):
|
|
1017
|
+
"""
|
|
1018
|
+
Change task RAM count
|
|
1019
|
+
|
|
1020
|
+
args:
|
|
1021
|
+
jediTaskID: jediTaskID of the task to change the priority
|
|
1022
|
+
ramCount: new ramCount for the task
|
|
1023
|
+
returns:
|
|
1024
|
+
status code
|
|
1025
|
+
0: communication succeeded to the panda server
|
|
1026
|
+
255: communication failure
|
|
1027
|
+
return code
|
|
1028
|
+
0: unknown task
|
|
1029
|
+
1: succeeded
|
|
1030
|
+
None: database error
|
|
1031
|
+
"""
|
|
1032
|
+
|
|
1033
|
+
http_client = HttpClient()
|
|
1034
|
+
|
|
1035
|
+
# execute
|
|
1036
|
+
url = f"{baseURLSSL}/changeTaskAttributePanda"
|
|
1037
|
+
data = {"jediTaskID": jediTaskID, "attrName": "ramCount", "attrValue": ramCount}
|
|
1038
|
+
status, output = http_client.post(url, data)
|
|
1039
|
+
try:
|
|
1040
|
+
return status, pickle_loads(output)
|
|
1041
|
+
except Exception:
|
|
1042
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1043
|
+
error_str = f"ERROR changeTaskRamCount : {error_type} {error_value}"
|
|
1044
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def changeTaskAttribute(jediTaskID, attrName, attrValue):
|
|
1048
|
+
"""
|
|
1049
|
+
Change task attribute
|
|
1050
|
+
|
|
1051
|
+
args:
|
|
1052
|
+
jediTaskID: jediTaskID of the task to change the attribute
|
|
1053
|
+
attrName: attribute name
|
|
1054
|
+
attrValue: new value for the attribute
|
|
1055
|
+
returns:
|
|
1056
|
+
status code
|
|
1057
|
+
0: communication succeeded to the panda server
|
|
1058
|
+
255: communication failure
|
|
1059
|
+
return: a tuple of return code and message
|
|
1060
|
+
0: unknown task
|
|
1061
|
+
1: succeeded
|
|
1062
|
+
2: disallowed to update the attribute
|
|
1063
|
+
None: database error
|
|
1064
|
+
"""
|
|
1065
|
+
|
|
1066
|
+
http_client = HttpClient()
|
|
1067
|
+
|
|
1068
|
+
# execute
|
|
1069
|
+
url = f"{baseURLSSL}/changeTaskAttributePanda"
|
|
1070
|
+
data = {"jediTaskID": jediTaskID, "attrName": attrName, "attrValue": attrValue}
|
|
1071
|
+
status, output = http_client.post(url, data)
|
|
1072
|
+
try:
|
|
1073
|
+
return status, pickle_loads(output)
|
|
1074
|
+
except Exception:
|
|
1075
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1076
|
+
error_str = f"ERROR changeTaskAttributePanda : {error_type} {error_value}"
|
|
1077
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def changeTaskSplitRule(jediTaskID, ruleName, ruleValue):
|
|
1081
|
+
"""
|
|
1082
|
+
Change split rule fo task
|
|
1083
|
+
|
|
1084
|
+
args:
|
|
1085
|
+
jediTaskID: jediTaskID of the task to change the rule
|
|
1086
|
+
ruleName: rule name
|
|
1087
|
+
ruleValue: new value for the rule
|
|
1088
|
+
returns:
|
|
1089
|
+
status code
|
|
1090
|
+
0: communication succeeded to the panda server
|
|
1091
|
+
255: communication failure
|
|
1092
|
+
return: a tuple of return code and message
|
|
1093
|
+
0: unknown task
|
|
1094
|
+
1: succeeded
|
|
1095
|
+
2: disallowed to update the attribute
|
|
1096
|
+
None: database error
|
|
1097
|
+
"""
|
|
1098
|
+
|
|
1099
|
+
http_client = HttpClient()
|
|
1100
|
+
|
|
1101
|
+
# execute
|
|
1102
|
+
url = f"{baseURLSSL}/changeTaskSplitRulePanda"
|
|
1103
|
+
data = {"jediTaskID": jediTaskID, "attrName": ruleName, "attrValue": ruleValue}
|
|
1104
|
+
status, output = http_client.post(url, data)
|
|
1105
|
+
try:
|
|
1106
|
+
return status, pickle_loads(output)
|
|
1107
|
+
except Exception:
|
|
1108
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1109
|
+
error_str = f"ERROR changeTaskSplitRule : {error_type} {error_value}"
|
|
1110
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def pauseTask(jediTaskID):
|
|
1114
|
+
"""
|
|
1115
|
+
Pause task
|
|
1116
|
+
|
|
1117
|
+
args:
|
|
1118
|
+
jediTaskID: jediTaskID of the task to pause
|
|
1119
|
+
returns:
|
|
1120
|
+
status code
|
|
1121
|
+
0: communication succeeded to the panda server
|
|
1122
|
+
255: communication failure
|
|
1123
|
+
tuple of return code and diagnostic message
|
|
1124
|
+
0: request is registered
|
|
1125
|
+
1: server error
|
|
1126
|
+
2: task not found
|
|
1127
|
+
3: permission denied
|
|
1128
|
+
4: irrelevant task status
|
|
1129
|
+
100: non SSL connection
|
|
1130
|
+
101: irrelevant taskID
|
|
1131
|
+
"""
|
|
1132
|
+
|
|
1133
|
+
http_client = HttpClient()
|
|
1134
|
+
|
|
1135
|
+
# execute
|
|
1136
|
+
url = f"{baseURLSSL}/pauseTask"
|
|
1137
|
+
data = {"jediTaskID": jediTaskID}
|
|
1138
|
+
status, output = http_client.post(url, data)
|
|
1139
|
+
try:
|
|
1140
|
+
return status, pickle_loads(output)
|
|
1141
|
+
except Exception:
|
|
1142
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1143
|
+
error_str = f"ERROR pauseTask : {error_type} {error_value}"
|
|
1144
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
def resumeTask(jediTaskID):
|
|
1148
|
+
"""
|
|
1149
|
+
Resume task
|
|
1150
|
+
|
|
1151
|
+
args:
|
|
1152
|
+
jediTaskID: jediTaskID of the task to release
|
|
1153
|
+
returns:
|
|
1154
|
+
status code
|
|
1155
|
+
0: communication succeeded to the panda server
|
|
1156
|
+
255: communication failure
|
|
1157
|
+
tuple of return code and diagnostic message
|
|
1158
|
+
0: request is registered
|
|
1159
|
+
1: server error
|
|
1160
|
+
2: task not found
|
|
1161
|
+
3: permission denied
|
|
1162
|
+
4: irrelevant task status
|
|
1163
|
+
100: non SSL connection
|
|
1164
|
+
101: irrelevant taskID
|
|
1165
|
+
"""
|
|
1166
|
+
|
|
1167
|
+
http_client = HttpClient()
|
|
1168
|
+
|
|
1169
|
+
# execute
|
|
1170
|
+
url = f"{baseURLSSL}/resumeTask"
|
|
1171
|
+
data = {"jediTaskID": jediTaskID}
|
|
1172
|
+
status, output = http_client.post(url, data)
|
|
1173
|
+
try:
|
|
1174
|
+
return status, pickle_loads(output)
|
|
1175
|
+
except Exception:
|
|
1176
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1177
|
+
error_str = f"ERROR resumeTask : {error_type} {error_value}"
|
|
1178
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def avalancheTask(jediTaskID):
|
|
1182
|
+
"""
|
|
1183
|
+
Force avalanche for task
|
|
1184
|
+
|
|
1185
|
+
args:
|
|
1186
|
+
jediTaskID: jediTaskID of the task to avalanche
|
|
1187
|
+
returns:
|
|
1188
|
+
status code
|
|
1189
|
+
0: communication succeeded to the panda server
|
|
1190
|
+
255: communication failure
|
|
1191
|
+
tuple of return code and diagnostic message
|
|
1192
|
+
0: request is registered
|
|
1193
|
+
1: server error
|
|
1194
|
+
2: task not found
|
|
1195
|
+
3: permission denied
|
|
1196
|
+
4: irrelevant task status
|
|
1197
|
+
100: non SSL connection
|
|
1198
|
+
101: irrelevant taskID
|
|
1199
|
+
"""
|
|
1200
|
+
|
|
1201
|
+
http_client = HttpClient()
|
|
1202
|
+
|
|
1203
|
+
# execute
|
|
1204
|
+
url = f"{baseURLSSL}/avalancheTask"
|
|
1205
|
+
data = {"jediTaskID": jediTaskID}
|
|
1206
|
+
status, output = http_client.post(url, data)
|
|
1207
|
+
try:
|
|
1208
|
+
return status, pickle_loads(output)
|
|
1209
|
+
except Exception:
|
|
1210
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1211
|
+
error_str = f"ERROR resumeTask : {error_type} {error_value}"
|
|
1212
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def increaseAttemptNr(jediTaskID, increase):
|
|
1216
|
+
"""
|
|
1217
|
+
Change task priority
|
|
1218
|
+
|
|
1219
|
+
args:
|
|
1220
|
+
jediTaskID: jediTaskID of the task to increase attempt numbers
|
|
1221
|
+
increase: increase for attempt numbers
|
|
1222
|
+
returns:
|
|
1223
|
+
status code
|
|
1224
|
+
0: communication succeeded to the panda server
|
|
1225
|
+
255: communication failure
|
|
1226
|
+
return code
|
|
1227
|
+
0: succeeded
|
|
1228
|
+
1: unknown task
|
|
1229
|
+
2: invalid task status
|
|
1230
|
+
3: permission denied
|
|
1231
|
+
4: wrong parameter
|
|
1232
|
+
None: database error
|
|
1233
|
+
"""
|
|
1234
|
+
|
|
1235
|
+
http_client = HttpClient()
|
|
1236
|
+
|
|
1237
|
+
# execute
|
|
1238
|
+
url = f"{baseURLSSL}/increaseAttemptNrPanda"
|
|
1239
|
+
data = {"jediTaskID": jediTaskID, "increasedNr": increase}
|
|
1240
|
+
status, output = http_client.post(url, data)
|
|
1241
|
+
try:
|
|
1242
|
+
return status, pickle_loads(output)
|
|
1243
|
+
except Exception:
|
|
1244
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1245
|
+
error_str = f"ERROR increaseAttemptNr : {error_type} {error_value}"
|
|
1246
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
def killUnfinishedJobs(jediTaskID, code=None, useMailAsID=False):
|
|
1250
|
+
"""
|
|
1251
|
+
Kill unfinished jobs in a task. Normal users can kill only their own jobs.
|
|
1252
|
+
People with production VOMS role can kill any jobs.
|
|
1253
|
+
Running jobs are killed when next heartbeat comes from the pilot.
|
|
1254
|
+
Set code=9 if running jobs need to be killed immediately.
|
|
1255
|
+
|
|
1256
|
+
args:
|
|
1257
|
+
jediTaskID: the taskID of the task
|
|
1258
|
+
code: specify why the jobs are killed
|
|
1259
|
+
2: expire
|
|
1260
|
+
3: aborted
|
|
1261
|
+
4: expire in waiting
|
|
1262
|
+
7: retry by server
|
|
1263
|
+
8: re-brokerage
|
|
1264
|
+
9: force kill
|
|
1265
|
+
50: kill by JEDI
|
|
1266
|
+
91: kill user jobs with prod role
|
|
1267
|
+
useMailAsID: obsolete
|
|
1268
|
+
returns:
|
|
1269
|
+
status code
|
|
1270
|
+
0: communication succeeded to the panda server
|
|
1271
|
+
255: communication failure
|
|
1272
|
+
the list of clouds (or Nones if tasks are not yet assigned)
|
|
1273
|
+
"""
|
|
1274
|
+
|
|
1275
|
+
http_client = HttpClient()
|
|
1276
|
+
|
|
1277
|
+
# execute
|
|
1278
|
+
url = f"{baseURLSSL}/killUnfinishedJobs"
|
|
1279
|
+
data = {"jediTaskID": jediTaskID, "code": code, "useMailAsID": useMailAsID}
|
|
1280
|
+
status, output = http_client.post(url, data)
|
|
1281
|
+
try:
|
|
1282
|
+
return status, pickle_loads(output)
|
|
1283
|
+
except Exception:
|
|
1284
|
+
error_type, error_value, _ = sys.exc_info()
|
|
1285
|
+
error_str = f"ERROR killUnfinishedJobs : {error_type} {error_value}"
|
|
1286
|
+
print(error_str)
|
|
1287
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
def triggerTaskBrokerage(jediTaskID):
|
|
1291
|
+
"""
|
|
1292
|
+
Trigger task brokerage
|
|
1293
|
+
|
|
1294
|
+
args:
|
|
1295
|
+
jediTaskID: jediTaskID of the task to change the attribute
|
|
1296
|
+
returns:
|
|
1297
|
+
status code
|
|
1298
|
+
0: communication succeeded to the panda server
|
|
1299
|
+
255: communication failure
|
|
1300
|
+
return: a tuple of return code and message
|
|
1301
|
+
0: unknown task
|
|
1302
|
+
1: succeeded
|
|
1303
|
+
None: database error
|
|
1304
|
+
"""
|
|
1305
|
+
|
|
1306
|
+
http_client = HttpClient()
|
|
1307
|
+
|
|
1308
|
+
# execute
|
|
1309
|
+
url = f"{baseURLSSL}/changeTaskModTimePanda"
|
|
1310
|
+
data = {"jediTaskID": jediTaskID, "diffValue": -12}
|
|
1311
|
+
status, output = http_client.post(url, data)
|
|
1312
|
+
try:
|
|
1313
|
+
return status, pickle_loads(output)
|
|
1314
|
+
except Exception:
|
|
1315
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1316
|
+
error_str = f"ERROR triggerTaskBrokerage : {error_type} {error_value}"
|
|
1317
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def getPandaIDsWithTaskID(jediTaskID):
|
|
1321
|
+
"""
|
|
1322
|
+
Get PanDA IDs with TaskID
|
|
1323
|
+
|
|
1324
|
+
args:
|
|
1325
|
+
jediTaskID: jediTaskID of the task to get lit of PanDA IDs
|
|
1326
|
+
returns:
|
|
1327
|
+
status code
|
|
1328
|
+
0: communication succeeded to the panda server
|
|
1329
|
+
255: communication failure
|
|
1330
|
+
the list of PanDA IDs
|
|
1331
|
+
"""
|
|
1332
|
+
|
|
1333
|
+
http_client = HttpClient()
|
|
1334
|
+
# execute
|
|
1335
|
+
url = f"{baseURL}/getPandaIDsWithTaskID"
|
|
1336
|
+
data = {"jediTaskID": jediTaskID}
|
|
1337
|
+
status, output = http_client.post(url, data)
|
|
1338
|
+
try:
|
|
1339
|
+
return status, pickle_loads(output)
|
|
1340
|
+
except Exception:
|
|
1341
|
+
error_type, error_value, _ = sys.exc_info()
|
|
1342
|
+
error_str = f"ERROR getPandaIDsWithTaskID : {error_type} {error_value}"
|
|
1343
|
+
print(error_str)
|
|
1344
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
def reactivateTask(jediTaskID, keep_attempt_nr=False, trigger_job_generation=False):
|
|
1348
|
+
"""
|
|
1349
|
+
Reactivate task
|
|
1350
|
+
|
|
1351
|
+
args:
|
|
1352
|
+
jediTaskID: jediTaskID of the task to be reactivated
|
|
1353
|
+
keep_attempt_nr: not to reset attempt numbers when being reactivated
|
|
1354
|
+
trigger_job_generation: trigger job generation once being reactivated
|
|
1355
|
+
returns:
|
|
1356
|
+
status code
|
|
1357
|
+
0: communication succeeded to the panda server
|
|
1358
|
+
255: communication failure
|
|
1359
|
+
return: a tuple of return code and message
|
|
1360
|
+
0: unknown task
|
|
1361
|
+
1: succeeded
|
|
1362
|
+
None: database error
|
|
1363
|
+
"""
|
|
1364
|
+
|
|
1365
|
+
http_client = HttpClient()
|
|
1366
|
+
|
|
1367
|
+
# execute
|
|
1368
|
+
url = f"{baseURLSSL}/reactivateTask"
|
|
1369
|
+
data = {"jediTaskID": jediTaskID}
|
|
1370
|
+
if keep_attempt_nr:
|
|
1371
|
+
data["keep_attempt_nr"] = True
|
|
1372
|
+
if trigger_job_generation:
|
|
1373
|
+
data["trigger_job_generation"] = True
|
|
1374
|
+
status, output = http_client.post(url, data)
|
|
1375
|
+
try:
|
|
1376
|
+
return status, pickle_loads(output)
|
|
1377
|
+
except Exception:
|
|
1378
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1379
|
+
error_str = f"ERROR reactivateTask : {error_type} {error_value}"
|
|
1380
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
def getTaskStatus(jediTaskID):
|
|
1384
|
+
"""
|
|
1385
|
+
Get task status for a particular task ID
|
|
1386
|
+
|
|
1387
|
+
args:
|
|
1388
|
+
jediTaskID: jediTaskID of the task to get lit of PanDA IDs
|
|
1389
|
+
returns:
|
|
1390
|
+
status code
|
|
1391
|
+
0: communication succeeded to the panda server
|
|
1392
|
+
255: communication failure
|
|
1393
|
+
the status string
|
|
1394
|
+
"""
|
|
1395
|
+
|
|
1396
|
+
http_client = HttpClient()
|
|
1397
|
+
# execute
|
|
1398
|
+
url = f"{baseURL}/getTaskStatus"
|
|
1399
|
+
data = {"jediTaskID": jediTaskID}
|
|
1400
|
+
status, output = http_client.post(url, data)
|
|
1401
|
+
try:
|
|
1402
|
+
return status, pickle_loads(output)
|
|
1403
|
+
except Exception:
|
|
1404
|
+
error_type, error_value, _ = sys.exc_info()
|
|
1405
|
+
error_str = f"ERROR getTaskStatus : {error_type} {error_value}"
|
|
1406
|
+
print(error_str)
|
|
1407
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def reassignShare(jedi_task_ids, share, reassign_running=False):
|
|
1411
|
+
"""
|
|
1412
|
+
Reassign specified tasks (and their jobs) to a new share
|
|
1413
|
+
|
|
1414
|
+
args:
|
|
1415
|
+
jedi_task_ids: task ids to act on
|
|
1416
|
+
share: share to be applied to jedi task ids
|
|
1417
|
+
returns:
|
|
1418
|
+
status code
|
|
1419
|
+
0: communication succeeded to the panda server
|
|
1420
|
+
255: communication failure
|
|
1421
|
+
return: a tuple of return code and message
|
|
1422
|
+
1: logical error
|
|
1423
|
+
0: success
|
|
1424
|
+
None: database error
|
|
1425
|
+
"""
|
|
1426
|
+
|
|
1427
|
+
http_client = HttpClient()
|
|
1428
|
+
|
|
1429
|
+
jedi_task_ids_pickle = pickle_dumps(jedi_task_ids)
|
|
1430
|
+
change_running_pickle = pickle_dumps(reassign_running)
|
|
1431
|
+
# execute
|
|
1432
|
+
url = f"{baseURLSSL}/reassignShare"
|
|
1433
|
+
data = {
|
|
1434
|
+
"jedi_task_ids_pickle": jedi_task_ids_pickle,
|
|
1435
|
+
"share": share,
|
|
1436
|
+
"reassign_running": change_running_pickle,
|
|
1437
|
+
}
|
|
1438
|
+
status, output = http_client.post(url, data)
|
|
1439
|
+
|
|
1440
|
+
try:
|
|
1441
|
+
return status, pickle_loads(output)
|
|
1442
|
+
except Exception:
|
|
1443
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1444
|
+
error_str = f"ERROR reassignShare : {error_type} {error_value}"
|
|
1445
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
def getTaskParamsMap(jediTaskID):
|
|
1449
|
+
"""
|
|
1450
|
+
Get task parameter map for a certain task ID
|
|
1451
|
+
|
|
1452
|
+
args:
|
|
1453
|
+
jediTaskID: jediTaskID of the task to get taskParamsMap
|
|
1454
|
+
returns:
|
|
1455
|
+
status code
|
|
1456
|
+
0: communication succeeded to the panda server
|
|
1457
|
+
255: communication failure
|
|
1458
|
+
return: a tuple of return code and taskParamsMap
|
|
1459
|
+
1: logical error
|
|
1460
|
+
0: success
|
|
1461
|
+
None: database error
|
|
1462
|
+
"""
|
|
1463
|
+
|
|
1464
|
+
http_client = HttpClient()
|
|
1465
|
+
# execute
|
|
1466
|
+
url = f"{baseURL}/getTaskParamsMap"
|
|
1467
|
+
data = {"jediTaskID": jediTaskID}
|
|
1468
|
+
status, output = http_client.post(url, data)
|
|
1469
|
+
try:
|
|
1470
|
+
return status, pickle_loads(output)
|
|
1471
|
+
except Exception:
|
|
1472
|
+
error_type, error_value, _ = sys.exc_info()
|
|
1473
|
+
error_str = f"ERROR getTaskParamsMap : {error_type} {error_value}"
|
|
1474
|
+
print(error_str)
|
|
1475
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
def setNumSlotsForWP(pandaQueueName, numSlots, gshare=None, resourceType=None, validPeriod=None):
|
|
1479
|
+
"""
|
|
1480
|
+
Set num slots for workload provisioning
|
|
1481
|
+
|
|
1482
|
+
args:
|
|
1483
|
+
pandaQueueName: Panda Queue name
|
|
1484
|
+
numSlots: the number of slots. 0 to dynamically set based on the number of starting jobs
|
|
1485
|
+
gshare: global share. None to set for any global share (default)
|
|
1486
|
+
resourceType: resource type. None to set for any resource type (default)
|
|
1487
|
+
validPeriod: How long the rule is valid in days. None if no expiration (default)
|
|
1488
|
+
returns:
|
|
1489
|
+
status code
|
|
1490
|
+
0: communication succeeded to the panda server
|
|
1491
|
+
255: communication failure
|
|
1492
|
+
tuple of return code and diagnostic message
|
|
1493
|
+
0: succeeded
|
|
1494
|
+
1: server error
|
|
1495
|
+
100: non SSL connection
|
|
1496
|
+
101: missing production role
|
|
1497
|
+
102: type error for some parameters
|
|
1498
|
+
"""
|
|
1499
|
+
|
|
1500
|
+
http_client = HttpClient()
|
|
1501
|
+
|
|
1502
|
+
# execute
|
|
1503
|
+
url = f"{baseURLSSL}/setNumSlotsForWP"
|
|
1504
|
+
data = {"pandaQueueName": pandaQueueName, "numSlots": numSlots}
|
|
1505
|
+
if gshare is not None:
|
|
1506
|
+
data["gshare"] = gshare
|
|
1507
|
+
if resourceType is not None:
|
|
1508
|
+
data["resourceType"] = resourceType
|
|
1509
|
+
if validPeriod is not None:
|
|
1510
|
+
data["validPeriod"] = validPeriod
|
|
1511
|
+
status, output = http_client.post(url, data)
|
|
1512
|
+
try:
|
|
1513
|
+
return status, json.loads(output)
|
|
1514
|
+
except Exception:
|
|
1515
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1516
|
+
error_str = f"ERROR setNumSlotsForWP : {error_type} {error_value}"
|
|
1517
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
# enable jumbo jobs
|
|
1521
|
+
def enableJumboJobs(jediTaskID, totalJumboJobs=1, nJumboPerSite=1):
|
|
1522
|
+
"""
|
|
1523
|
+
Enable jumbo jobs for a task
|
|
1524
|
+
|
|
1525
|
+
args:
|
|
1526
|
+
jediTaskID: jediTaskID of the task
|
|
1527
|
+
totalJumboJobs: The total number of active jumbo jobs produced for the task. Use 0 to disable jumbo jobs for the task
|
|
1528
|
+
nJumboPerSite: The number of active jumbo jobs per site
|
|
1529
|
+
returns:
|
|
1530
|
+
status code
|
|
1531
|
+
0: communication succeeded to the panda server
|
|
1532
|
+
255: communication failure
|
|
1533
|
+
tuple of return code and diagnostic message
|
|
1534
|
+
0: succeeded
|
|
1535
|
+
1: server error
|
|
1536
|
+
100: non SSL connection
|
|
1537
|
+
101: missing production role
|
|
1538
|
+
102: type error for some parameters
|
|
1539
|
+
"""
|
|
1540
|
+
|
|
1541
|
+
http_client = HttpClient()
|
|
1542
|
+
|
|
1543
|
+
# execute
|
|
1544
|
+
url = f"{baseURLSSL}/enableJumboJobs"
|
|
1545
|
+
data = {
|
|
1546
|
+
"jediTaskID": jediTaskID,
|
|
1547
|
+
"nJumboJobs": totalJumboJobs,
|
|
1548
|
+
"nJumboPerSite": nJumboPerSite,
|
|
1549
|
+
}
|
|
1550
|
+
status, output = http_client.post(url, data)
|
|
1551
|
+
try:
|
|
1552
|
+
return status, json.loads(output)
|
|
1553
|
+
except Exception:
|
|
1554
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1555
|
+
error_str = f"ERROR /enableJumboJobs : {error_type} {error_value}"
|
|
1556
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
def sweepPQ(panda_queue, status_list, ce_list, submission_host_list):
|
|
1560
|
+
"""
|
|
1561
|
+
Send a harvester command to panda server in order sweep a panda queue
|
|
1562
|
+
|
|
1563
|
+
args:
|
|
1564
|
+
panda_queue: panda queue name
|
|
1565
|
+
status_list: list with statuses to sweep, e.g. ['submitted']
|
|
1566
|
+
ce_list: list of CEs belonging to the site or 'ALL'
|
|
1567
|
+
submission_host_list: list of submission hosts this applies or 'ALL'
|
|
1568
|
+
returns:
|
|
1569
|
+
status code
|
|
1570
|
+
0: communication succeeded to the panda server
|
|
1571
|
+
255: communication failure
|
|
1572
|
+
return: a tuple of return code and message
|
|
1573
|
+
False: logical error
|
|
1574
|
+
True: success
|
|
1575
|
+
"""
|
|
1576
|
+
|
|
1577
|
+
http_client = HttpClient()
|
|
1578
|
+
|
|
1579
|
+
panda_queue_json = json.dumps(panda_queue)
|
|
1580
|
+
status_list_json = json.dumps(status_list)
|
|
1581
|
+
ce_list_json = json.dumps(ce_list)
|
|
1582
|
+
submission_host_list_json = json.dumps(submission_host_list)
|
|
1583
|
+
|
|
1584
|
+
# execute
|
|
1585
|
+
url = f"{baseURLSSL}/sweepPQ"
|
|
1586
|
+
data = {
|
|
1587
|
+
"panda_queue": panda_queue_json,
|
|
1588
|
+
"status_list": status_list_json,
|
|
1589
|
+
"ce_list": ce_list_json,
|
|
1590
|
+
"submission_host_list": submission_host_list_json,
|
|
1591
|
+
}
|
|
1592
|
+
status, output = http_client.post(url, data)
|
|
1593
|
+
|
|
1594
|
+
try:
|
|
1595
|
+
return status, json.loads(output)
|
|
1596
|
+
except Exception:
|
|
1597
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
1598
|
+
error_str = f"ERROR sweepPQ : {error_type} {error_value}"
|
|
1599
|
+
return EC_Failed, f"{output}\n{error_str}"
|
|
1600
|
+
|
|
1601
|
+
|
|
1602
|
+
def send_command_to_job(panda_id, command):
|
|
1603
|
+
"""
|
|
1604
|
+
Send a command to a job
|
|
1605
|
+
|
|
1606
|
+
args:
|
|
1607
|
+
panda_id: PandaID of the job
|
|
1608
|
+
com: a command string passed to the pilot. max 250 chars
|
|
1609
|
+
returns:
|
|
1610
|
+
status code
|
|
1611
|
+
0: communication succeeded to the panda server
|
|
1612
|
+
255: communication failure
|
|
1613
|
+
return: a tuple of return code and message
|
|
1614
|
+
False: failed
|
|
1615
|
+
True: the command received
|
|
1616
|
+
"""
|
|
1617
|
+
|
|
1618
|
+
http_client = HttpClientV1()
|
|
1619
|
+
url = f"{api_url_ssl_v1}/job/set_command"
|
|
1620
|
+
data = {"job_id": panda_id, "command": command}
|
|
1621
|
+
|
|
1622
|
+
status, output = http_client.post(url, data)
|
|
1623
|
+
|
|
1624
|
+
return status, output
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
def get_banned_users():
|
|
1628
|
+
"""
|
|
1629
|
+
Get list of banned users
|
|
1630
|
+
|
|
1631
|
+
returns:
|
|
1632
|
+
status code
|
|
1633
|
+
True: communication succeeded to the panda server
|
|
1634
|
+
False: communication failure
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
"""
|
|
1638
|
+
|
|
1639
|
+
http_client = HttpClientV1()
|
|
1640
|
+
url = f"{api_url_ssl_v1}/metaconfig/get_banned_users"
|
|
1641
|
+
|
|
1642
|
+
status, output = http_client.get(url, {})
|
|
1643
|
+
if status != 0:
|
|
1644
|
+
return False, f"bad response: {output}"
|
|
1645
|
+
|
|
1646
|
+
success = output["success"]
|
|
1647
|
+
message = output.get("message", "")
|
|
1648
|
+
data = output.get("data", None)
|
|
1649
|
+
|
|
1650
|
+
if success:
|
|
1651
|
+
return True, data
|
|
1652
|
+
else:
|
|
1653
|
+
return False, f"error message: {message}"
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
def release_task(jedi_task_id):
|
|
1657
|
+
"""
|
|
1658
|
+
Release task from staging
|
|
1659
|
+
|
|
1660
|
+
args:
|
|
1661
|
+
jedi_task_id: jediTaskID of the task to avalanche
|
|
1662
|
+
returns:
|
|
1663
|
+
status code
|
|
1664
|
+
0: communication succeeded to the panda server
|
|
1665
|
+
255: communication failure
|
|
1666
|
+
tuple of return code and diagnostic message
|
|
1667
|
+
0: request is registered
|
|
1668
|
+
1: server error
|
|
1669
|
+
2: task not found
|
|
1670
|
+
3: permission denied
|
|
1671
|
+
4: irrelevant task status
|
|
1672
|
+
100: non SSL connection
|
|
1673
|
+
101: irrelevant taskID
|
|
1674
|
+
"""
|
|
1675
|
+
|
|
1676
|
+
http_client = HttpClient()
|
|
1677
|
+
|
|
1678
|
+
# execute
|
|
1679
|
+
url = f"{baseURLSSL}/release_task"
|
|
1680
|
+
data = {"jedi_task_id": jedi_task_id}
|
|
1681
|
+
status, output = http_client.post(url, data)
|
|
1682
|
+
try:
|
|
1683
|
+
return status, json.loads(output)
|
|
1684
|
+
except Exception as e:
|
|
1685
|
+
error_str = f"ERROR release_task : failed with {str(e)}"
|
|
1686
|
+
return EC_Failed, f"{output}\n{error_str}"
|