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,848 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
import gzip
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import struct
|
|
7
|
+
import sys
|
|
8
|
+
import traceback
|
|
9
|
+
import uuid
|
|
10
|
+
import zlib
|
|
11
|
+
from typing import Generator
|
|
12
|
+
|
|
13
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
14
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
15
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
16
|
+
from werkzeug.datastructures import FileStorage
|
|
17
|
+
|
|
18
|
+
from pandaserver.config import panda_config
|
|
19
|
+
from pandaserver.jobdispatcher import Protocol
|
|
20
|
+
from pandaserver.srvcore import CoreUtils
|
|
21
|
+
from pandaserver.srvcore.panda_request import PandaRequest
|
|
22
|
+
from pandaserver.userinterface import Client
|
|
23
|
+
|
|
24
|
+
_logger = PandaLogger().getLogger("Utils")
|
|
25
|
+
|
|
26
|
+
IGNORED_SUFFIX = [".out"]
|
|
27
|
+
|
|
28
|
+
# File size limits
|
|
29
|
+
MB = 1024 * 1024
|
|
30
|
+
EVENT_PICKING_LIMIT = 10 * MB
|
|
31
|
+
LOG_LIMIT = 100 * MB
|
|
32
|
+
CHECKPOINT_LIMIT = 500 * MB
|
|
33
|
+
SANDBOX_NO_BUILD_LIMIT = 100 * MB
|
|
34
|
+
SANDBOX_LIMIT = 768 * MB
|
|
35
|
+
|
|
36
|
+
# Error messages
|
|
37
|
+
ERROR_NOT_SECURE = "ERROR : no HTTPS"
|
|
38
|
+
ERROR_LIMITED_PROXY = "ERROR: rejected due to the usage of limited proxy"
|
|
39
|
+
ERROR_OVERWRITE = "ERROR: cannot overwrite file"
|
|
40
|
+
ERROR_WRITE = "ERROR: cannot write file"
|
|
41
|
+
ERROR_SIZE_LIMIT = "ERROR: upload failure. Exceeded size limit"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def isAlive(panda_request: PandaRequest) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Check if the server is alive. Basic function for the health check and used in SLS monitoring.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
panda_request (PandaRequest): PanDA request object.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
str: "alive=yes"
|
|
53
|
+
"""
|
|
54
|
+
return "alive=yes"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_content_length(panda_request: PandaRequest, tmp_log: LogWrapper) -> int:
|
|
58
|
+
"""
|
|
59
|
+
Get the content length of the request.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
panda_request (PandaRequest): PanDA request object.
|
|
63
|
+
tmp_log (LogWrapper): logger object of the calling function.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
int: content length of the request.
|
|
67
|
+
"""
|
|
68
|
+
content_length = 0
|
|
69
|
+
try:
|
|
70
|
+
content_length = int(panda_request.headers_in["content-length"])
|
|
71
|
+
except Exception:
|
|
72
|
+
if "content-length" in panda_request.headers_in:
|
|
73
|
+
tmp_log.error(f"cannot get content_length: {panda_request.headers_in['content-length']}")
|
|
74
|
+
else:
|
|
75
|
+
tmp_log.error("no content_length for {method_name}")
|
|
76
|
+
|
|
77
|
+
tmp_log.debug(f"size {content_length}")
|
|
78
|
+
return content_length
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# upload file
|
|
82
|
+
def putFile(panda_request: PandaRequest, file: FileStorage) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Upload a file to the server.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
panda_request (PandaRequest): PanDA request object.
|
|
88
|
+
file (FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
string: "True" if the upload was successful, otherwise an error message.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
tmp_log = LogWrapper(_logger, f"putFile-{naive_utcnow().isoformat('/')}")
|
|
95
|
+
tmp_log.debug(f"start")
|
|
96
|
+
|
|
97
|
+
# check if using secure connection and the proxy is not limited
|
|
98
|
+
if not Protocol.isSecure(panda_request):
|
|
99
|
+
tmp_log.error("no HTTPS")
|
|
100
|
+
tmp_log.debug("trigger garbage collection")
|
|
101
|
+
gc.collect()
|
|
102
|
+
tmp_log.debug("end")
|
|
103
|
+
return ERROR_NOT_SECURE
|
|
104
|
+
if "/CN=limited proxy" in panda_request.subprocess_env["SSL_CLIENT_S_DN"]:
|
|
105
|
+
tmp_log.error("limited proxy is used")
|
|
106
|
+
tmp_log.debug("trigger garbage collection")
|
|
107
|
+
gc.collect()
|
|
108
|
+
tmp_log.debug("end")
|
|
109
|
+
return ERROR_LIMITED_PROXY
|
|
110
|
+
|
|
111
|
+
# user name
|
|
112
|
+
user_name = CoreUtils.clean_user_id(panda_request.subprocess_env["SSL_CLIENT_S_DN"])
|
|
113
|
+
tmp_log.debug(f"user_name={user_name} file_path={file.filename}")
|
|
114
|
+
|
|
115
|
+
# get file size limit
|
|
116
|
+
if not file.filename.startswith("sources."):
|
|
117
|
+
no_build = True
|
|
118
|
+
size_limit = SANDBOX_NO_BUILD_LIMIT
|
|
119
|
+
else:
|
|
120
|
+
no_build = False
|
|
121
|
+
size_limit = SANDBOX_LIMIT
|
|
122
|
+
|
|
123
|
+
# get actual file size
|
|
124
|
+
content_length = get_content_length(panda_request, tmp_log)
|
|
125
|
+
|
|
126
|
+
# check if we are above the size limit
|
|
127
|
+
if content_length > size_limit:
|
|
128
|
+
error_message = f"{ERROR_SIZE_LIMIT} {content_length}>{size_limit}."
|
|
129
|
+
if no_build:
|
|
130
|
+
error_message += " Please submit the job without --noBuild/--libDS since those options impose a tighter size limit"
|
|
131
|
+
else:
|
|
132
|
+
error_message += " Please remove redundant files from your work area"
|
|
133
|
+
tmp_log.error(error_message)
|
|
134
|
+
tmp_log.debug("trigger garbage collection")
|
|
135
|
+
gc.collect()
|
|
136
|
+
tmp_log.debug("end")
|
|
137
|
+
return error_message
|
|
138
|
+
|
|
139
|
+
# write to file
|
|
140
|
+
try:
|
|
141
|
+
file_name = file.filename.split("/")[-1]
|
|
142
|
+
full_path = f"{panda_config.cache_dir}/{file_name}"
|
|
143
|
+
|
|
144
|
+
# avoid overwriting
|
|
145
|
+
if os.path.exists(full_path) and file.filename.split(".")[-1] != "__ow__":
|
|
146
|
+
# touch
|
|
147
|
+
os.utime(full_path, None)
|
|
148
|
+
# send error message
|
|
149
|
+
error_message = ERROR_OVERWRITE
|
|
150
|
+
tmp_log.debug(f"{ERROR_OVERWRITE} {file_name}")
|
|
151
|
+
tmp_log.debug("end")
|
|
152
|
+
return error_message
|
|
153
|
+
|
|
154
|
+
# write the file to the cache directory
|
|
155
|
+
with open(full_path, "wb") as file_object:
|
|
156
|
+
file_content = file.read()
|
|
157
|
+
if hasattr(panda_config, "compress_file_names") and [
|
|
158
|
+
True for patt in panda_config.compress_file_names.split(",") if re.search(patt, file_name) is not None
|
|
159
|
+
]:
|
|
160
|
+
file_content = gzip.compress(file_content)
|
|
161
|
+
file_object.write(file_content)
|
|
162
|
+
|
|
163
|
+
except Exception:
|
|
164
|
+
error_message = ERROR_WRITE
|
|
165
|
+
tmp_log.error(error_message)
|
|
166
|
+
tmp_log.debug("trigger garbage collection")
|
|
167
|
+
gc.collect()
|
|
168
|
+
tmp_log.debug("end")
|
|
169
|
+
return error_message
|
|
170
|
+
|
|
171
|
+
# calculate the checksum
|
|
172
|
+
try:
|
|
173
|
+
# decode Footer
|
|
174
|
+
footer = file_content[-8:]
|
|
175
|
+
checksum, _ = struct.unpack("II", footer)
|
|
176
|
+
checksum = str(checksum)
|
|
177
|
+
tmp_log.debug(f"CRC from gzip Footer {checksum}")
|
|
178
|
+
except Exception:
|
|
179
|
+
# use None to avoid delay for now
|
|
180
|
+
checksum = ""
|
|
181
|
+
tmp_log.debug(f"No CRC calculated")
|
|
182
|
+
|
|
183
|
+
# calculate the file size
|
|
184
|
+
file_size = len(file_content)
|
|
185
|
+
|
|
186
|
+
# log the full file information
|
|
187
|
+
tmp_log.debug(f"written dn={user_name} file={full_path} size={file_size} crc={checksum}")
|
|
188
|
+
|
|
189
|
+
# record the file information to DB
|
|
190
|
+
if panda_config.record_sandbox_info:
|
|
191
|
+
# ignore some suffixes, e.g. out
|
|
192
|
+
to_insert = True
|
|
193
|
+
for patt in IGNORED_SUFFIX:
|
|
194
|
+
if file.filename.endswith(patt):
|
|
195
|
+
to_insert = False
|
|
196
|
+
break
|
|
197
|
+
if not to_insert:
|
|
198
|
+
tmp_log.debug("skipped to insert to DB")
|
|
199
|
+
else:
|
|
200
|
+
status_client, output_client = Client.register_cache_file(user_name, file.filename, file_size, checksum)
|
|
201
|
+
if status_client != 0:
|
|
202
|
+
error_message = f"ERROR : failed to register sandbox to DB with {status_client} {output_client}"
|
|
203
|
+
tmp_log.error(error_message)
|
|
204
|
+
tmp_log.debug("Done")
|
|
205
|
+
return error_message
|
|
206
|
+
|
|
207
|
+
success = output_client["success"]
|
|
208
|
+
message = output_client["message"]
|
|
209
|
+
if not success:
|
|
210
|
+
error_message = f"ERROR : failed to register sandbox to DB with {message}"
|
|
211
|
+
tmp_log.error(error_message)
|
|
212
|
+
tmp_log.debug("Done")
|
|
213
|
+
return error_message
|
|
214
|
+
|
|
215
|
+
tmp_log.debug(f"Inserted sandbox to DB with {output_client}")
|
|
216
|
+
|
|
217
|
+
tmp_log.debug("trigger garbage collection")
|
|
218
|
+
gc.collect()
|
|
219
|
+
tmp_log.debug("Done")
|
|
220
|
+
|
|
221
|
+
return "True"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def putEventPickingRequest(
|
|
225
|
+
panda_request: PandaRequest,
|
|
226
|
+
runEventList="",
|
|
227
|
+
eventPickDataType="",
|
|
228
|
+
eventPickStreamName="",
|
|
229
|
+
eventPickDS="",
|
|
230
|
+
eventPickAmiTag="",
|
|
231
|
+
userDatasetName="",
|
|
232
|
+
lockedBy="",
|
|
233
|
+
params="",
|
|
234
|
+
inputFileList="",
|
|
235
|
+
eventPickNumSites="",
|
|
236
|
+
userTaskName="",
|
|
237
|
+
ei_api="",
|
|
238
|
+
giveGUID=None,
|
|
239
|
+
) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Upload event picking request to the server.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
panda_request (PandaRequest): PanDA request object.
|
|
245
|
+
runEventList (str): run and event list.
|
|
246
|
+
eventPickDataType (str): data type.
|
|
247
|
+
eventPickStreamName (str): stream name.
|
|
248
|
+
eventPickDS (str): dataset name.
|
|
249
|
+
eventPickAmiTag (str): AMI tag.
|
|
250
|
+
userDatasetName (str): user dataset name.
|
|
251
|
+
lockedBy (str): locking agent.
|
|
252
|
+
params (str): parameters.
|
|
253
|
+
inputFileList (str): input file list.
|
|
254
|
+
eventPickNumSites (str): number of sites.
|
|
255
|
+
userTaskName (str): user task name.
|
|
256
|
+
ei_api (str): event index API.
|
|
257
|
+
giveGUID (str): give GUID.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
string: "True" if the upload was successful, otherwise an error message.
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
if not Protocol.isSecure(panda_request):
|
|
264
|
+
return ERROR_NOT_SECURE
|
|
265
|
+
|
|
266
|
+
user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
|
|
267
|
+
|
|
268
|
+
tmp_log = LogWrapper(_logger, f"putEventPickingRequest-{naive_utcnow().isoformat('/')}")
|
|
269
|
+
tmp_log.debug(f"start for {user_name}")
|
|
270
|
+
|
|
271
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
272
|
+
|
|
273
|
+
# get total size
|
|
274
|
+
try:
|
|
275
|
+
content_length = int(panda_request.headers_in["content-length"])
|
|
276
|
+
except Exception:
|
|
277
|
+
error_message = "cannot get content-length from HTTP request."
|
|
278
|
+
tmp_log.error(f"{error_message}")
|
|
279
|
+
tmp_log.debug("end")
|
|
280
|
+
return "ERROR : " + error_message
|
|
281
|
+
tmp_log.debug(f"size {content_length}")
|
|
282
|
+
|
|
283
|
+
if content_length > EVENT_PICKING_LIMIT:
|
|
284
|
+
error_message = f"Run/event list is too large. Exceeded size limit {content_length}>{EVENT_PICKING_LIMIT}."
|
|
285
|
+
tmp_log.error(f"{error_message} ")
|
|
286
|
+
tmp_log.debug("end")
|
|
287
|
+
return "ERROR : " + error_message
|
|
288
|
+
|
|
289
|
+
if giveGUID == "True":
|
|
290
|
+
giveGUID = True
|
|
291
|
+
else:
|
|
292
|
+
giveGUID = False
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# generate the filename
|
|
296
|
+
file_name = f"{panda_config.cache_dir}/evp.{str(uuid.uuid4())}"
|
|
297
|
+
tmp_log.debug(f"file: {file_name}")
|
|
298
|
+
|
|
299
|
+
# write the information to file
|
|
300
|
+
file_content = (
|
|
301
|
+
f"userName={user_name}\n"
|
|
302
|
+
f"creationTime={creation_time}\n"
|
|
303
|
+
f"eventPickDataType={eventPickDataType}\n"
|
|
304
|
+
f"eventPickStreamName={eventPickStreamName}\n"
|
|
305
|
+
f"eventPickDS={eventPickDS}\n"
|
|
306
|
+
f"eventPickAmiTag={eventPickAmiTag}\n"
|
|
307
|
+
f"eventPickNumSites={eventPickNumSites}\n"
|
|
308
|
+
f"userTaskName={userTaskName}\n"
|
|
309
|
+
f"userDatasetName={userDatasetName}\n"
|
|
310
|
+
f"lockedBy={lockedBy}\n"
|
|
311
|
+
f"params={params}\n"
|
|
312
|
+
f"inputFileList={inputFileList}\n"
|
|
313
|
+
f"ei_api={ei_api}\n"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
with open(file_name, "w") as file_object:
|
|
317
|
+
file_object.write(file_content)
|
|
318
|
+
run_event_guid_map = {}
|
|
319
|
+
for tmp_line in runEventList.split("\n"):
|
|
320
|
+
tmp_items = tmp_line.split()
|
|
321
|
+
if (len(tmp_items) != 2 and not giveGUID) or (len(tmp_items) != 3 and giveGUID):
|
|
322
|
+
continue
|
|
323
|
+
file_object.write("runEvent=%s,%s\n" % tuple(tmp_items[:2]))
|
|
324
|
+
if giveGUID:
|
|
325
|
+
run_event_guid_map[tuple(tmp_items[:2])] = [tmp_items[2]]
|
|
326
|
+
file_object.write(f"runEvtGuidMap={str(run_event_guid_map)}\n")
|
|
327
|
+
|
|
328
|
+
except Exception as e:
|
|
329
|
+
error_message = f"cannot put request due to {str(e)}"
|
|
330
|
+
tmp_log.error(error_message + traceback.format_exc())
|
|
331
|
+
return f"ERROR : {error_message}"
|
|
332
|
+
|
|
333
|
+
tmp_log.debug("end")
|
|
334
|
+
return "True"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# upload lost file recovery request
|
|
338
|
+
def put_file_recovery_request(panda_request: PandaRequest, jediTaskID: str, dryRun: bool = None) -> str:
|
|
339
|
+
"""
|
|
340
|
+
Upload lost file recovery request to the server.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
panda_request (PandaRequest): PanDA request object.
|
|
344
|
+
jediTaskID (string): task ID.
|
|
345
|
+
dryRun (bool): dry run flag.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
string: String in json format with (boolean, message)
|
|
349
|
+
"""
|
|
350
|
+
if not Protocol.isSecure(panda_request):
|
|
351
|
+
return json.dumps((False, ERROR_NOT_SECURE))
|
|
352
|
+
user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
|
|
353
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
354
|
+
|
|
355
|
+
tmp_log = LogWrapper(_logger, f"put_file_recovery_request < jediTaskID={jediTaskID}")
|
|
356
|
+
tmp_log.debug(f"start user={user_name}")
|
|
357
|
+
# get total size
|
|
358
|
+
try:
|
|
359
|
+
jedi_task_id = int(jediTaskID)
|
|
360
|
+
|
|
361
|
+
# generate the filename
|
|
362
|
+
file_name = f"{panda_config.cache_dir}/recov.{str(uuid.uuid4())}"
|
|
363
|
+
tmp_log.debug(f"file={file_name}")
|
|
364
|
+
|
|
365
|
+
# write the file content
|
|
366
|
+
with open(file_name, "w") as file_object:
|
|
367
|
+
data = {
|
|
368
|
+
"userName": user_name,
|
|
369
|
+
"creationTime": creation_time,
|
|
370
|
+
"jediTaskID": jedi_task_id,
|
|
371
|
+
}
|
|
372
|
+
if dryRun:
|
|
373
|
+
data["dryRun"] = True
|
|
374
|
+
|
|
375
|
+
json.dump(data, file_object)
|
|
376
|
+
except Exception as exc:
|
|
377
|
+
error_message = f"cannot put request due to {str(exc)} "
|
|
378
|
+
tmp_log.error(error_message + traceback.format_exc())
|
|
379
|
+
return json.dumps((False, error_message))
|
|
380
|
+
|
|
381
|
+
tmp_log.debug("done")
|
|
382
|
+
return json.dumps((True, "request was accepted and will be processed in a few minutes"))
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def put_workflow_request(panda_request: PandaRequest, data: str, check: bool = False, sync: bool = False) -> str:
|
|
386
|
+
"""
|
|
387
|
+
Upload workflow request to the server.
|
|
388
|
+
Args:
|
|
389
|
+
panda_request (PandaRequest): PanDA request object.
|
|
390
|
+
data (string): workflow request data.
|
|
391
|
+
check (bool): check flag.
|
|
392
|
+
sync (bool): synchronous processing.
|
|
393
|
+
Returns:
|
|
394
|
+
string: String in json format with (boolean, message)
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
if not Protocol.isSecure(panda_request):
|
|
398
|
+
return json.dumps((False, ERROR_NOT_SECURE))
|
|
399
|
+
|
|
400
|
+
user_name = panda_request.subprocess_env["SSL_CLIENT_S_DN"]
|
|
401
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
402
|
+
|
|
403
|
+
tmp_log = LogWrapper(_logger, "put_workflow_request")
|
|
404
|
+
|
|
405
|
+
tmp_log.debug(f"start user={user_name} check={check}")
|
|
406
|
+
|
|
407
|
+
if check in ("True", True):
|
|
408
|
+
check = True
|
|
409
|
+
elif sync in ("True", True):
|
|
410
|
+
sync = True
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
# generate the filename
|
|
414
|
+
file_name = f"{panda_config.cache_dir}/workflow.{str(uuid.uuid4())}"
|
|
415
|
+
tmp_log.debug(f"file={file_name}")
|
|
416
|
+
|
|
417
|
+
# write
|
|
418
|
+
with open(file_name, "w") as file_object:
|
|
419
|
+
data_dict = {
|
|
420
|
+
"userName": user_name,
|
|
421
|
+
"creationTime": creation_time,
|
|
422
|
+
"data": json.loads(data),
|
|
423
|
+
}
|
|
424
|
+
json.dump(data_dict, file_object)
|
|
425
|
+
|
|
426
|
+
if sync or check:
|
|
427
|
+
from pandaserver.taskbuffer.workflow_processor import WorkflowProcessor
|
|
428
|
+
|
|
429
|
+
processor = WorkflowProcessor(log_stream=_logger)
|
|
430
|
+
if check:
|
|
431
|
+
ret = processor.process(file_name, True, True, True, True)
|
|
432
|
+
else:
|
|
433
|
+
ret = processor.process(file_name, True, False, True, False)
|
|
434
|
+
if os.path.exists(file_name):
|
|
435
|
+
try:
|
|
436
|
+
os.remove(file_name)
|
|
437
|
+
except Exception:
|
|
438
|
+
pass
|
|
439
|
+
tmp_log.debug("done")
|
|
440
|
+
return json.dumps((True, ret))
|
|
441
|
+
|
|
442
|
+
except Exception as exc:
|
|
443
|
+
error_message = f"cannot put request due to {str(exc)} "
|
|
444
|
+
tmp_log.error(error_message + traceback.format_exc())
|
|
445
|
+
return json.dumps((False, error_message))
|
|
446
|
+
|
|
447
|
+
tmp_log.debug("done")
|
|
448
|
+
return json.dumps((True, "request was accepted and will be processed in a few minutes"))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# delete file
|
|
452
|
+
def deleteFile(panda_request: PandaRequest, file: FileStorage) -> str:
|
|
453
|
+
"""
|
|
454
|
+
Delete a file from the cache directory.
|
|
455
|
+
Args:
|
|
456
|
+
panda_request (PandaRequest): PanDA request object.
|
|
457
|
+
file (string): file name to be deleted
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
string: String with "True" or "False"
|
|
461
|
+
"""
|
|
462
|
+
if not Protocol.isSecure(panda_request):
|
|
463
|
+
return ERROR_NOT_SECURE
|
|
464
|
+
|
|
465
|
+
try:
|
|
466
|
+
# may be reused for re-brokerage
|
|
467
|
+
# os.remove('%s/%s' % (panda_config.cache_dir,file.split('/')[-1]))
|
|
468
|
+
return "True"
|
|
469
|
+
except Exception:
|
|
470
|
+
return "False"
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# touch file
|
|
474
|
+
def touchFile(panda_request: PandaRequest, filename: str) -> str:
|
|
475
|
+
"""
|
|
476
|
+
Touch a file in the cache directory.
|
|
477
|
+
Args:
|
|
478
|
+
panda_request (PandaRequest): PanDA request object.
|
|
479
|
+
filename (string): file name to be deleted
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
string: String with "True" or "False"
|
|
483
|
+
"""
|
|
484
|
+
if not Protocol.isSecure(panda_request):
|
|
485
|
+
return "False"
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
os.utime(f"{panda_config.cache_dir}/{filename.split('/')[-1]}", None)
|
|
489
|
+
return "True"
|
|
490
|
+
except Exception:
|
|
491
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
492
|
+
_logger.error(f"touchFile : {error_type} {error_value}")
|
|
493
|
+
return "False"
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# get server name:port for SSL
|
|
497
|
+
def getServer(panda_request: PandaRequest) -> str:
|
|
498
|
+
"""
|
|
499
|
+
Get the server name and port for HTTPS.
|
|
500
|
+
Args:
|
|
501
|
+
panda_request (PandaRequest): PanDA request object.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
string: String with server:port
|
|
505
|
+
"""
|
|
506
|
+
return f"{panda_config.pserverhost}:{panda_config.pserverport}"
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# get server name:port for HTTP
|
|
510
|
+
def getServerHTTP(panda_request: PandaRequest) -> str:
|
|
511
|
+
"""
|
|
512
|
+
Get the HTTP server name and port for HTTP.
|
|
513
|
+
Args:
|
|
514
|
+
panda_request (PandaRequest): PanDA request object.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
string: String with server:port
|
|
518
|
+
"""
|
|
519
|
+
return f"{panda_config.pserverhosthttp}:{panda_config.pserverporthttp}"
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def updateLog(panda_request: PandaRequest, file: FileStorage) -> str:
|
|
523
|
+
"""
|
|
524
|
+
Update the log file, appending more content at the end of the file.
|
|
525
|
+
Args:
|
|
526
|
+
panda_request (PandaRequest): PanDA request object.
|
|
527
|
+
file (FileStorage): werkzeug.FileStorage object to be updated.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
string: String with "True" or error message
|
|
531
|
+
"""
|
|
532
|
+
tmp_log = LogWrapper(_logger, f"updateLog < {file.filename} >")
|
|
533
|
+
tmp_log.debug("start")
|
|
534
|
+
|
|
535
|
+
# write to file
|
|
536
|
+
try:
|
|
537
|
+
# expand
|
|
538
|
+
new_content = zlib.decompress(file.read())
|
|
539
|
+
|
|
540
|
+
# stdout name
|
|
541
|
+
log_name = f"{panda_config.cache_dir}/{file.filename.split('/')[-1]}"
|
|
542
|
+
|
|
543
|
+
# append to file end
|
|
544
|
+
with open(log_name, "a") as file_object:
|
|
545
|
+
file_object.write(new_content)
|
|
546
|
+
|
|
547
|
+
except Exception:
|
|
548
|
+
error_type, error_value, _ = sys.exc_info()
|
|
549
|
+
tmp_log.error(f"{error_type} {error_value}")
|
|
550
|
+
return f"ERROR: cannot update file with {error_type} {error_value}"
|
|
551
|
+
|
|
552
|
+
tmp_log.debug("end")
|
|
553
|
+
return "True"
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def fetchLog(panda_request: PandaRequest, logName: str, offset: int = 0) -> str:
|
|
557
|
+
"""
|
|
558
|
+
Fetch the log file, if required at a particular offset.
|
|
559
|
+
Args:
|
|
560
|
+
panda_request (PandaRequest): PanDA request object.
|
|
561
|
+
logName (string): log file name
|
|
562
|
+
offset (int): offset in the file
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
string: String with the log content
|
|
566
|
+
"""
|
|
567
|
+
tmp_log = LogWrapper(_logger, f"fetchLog <{logName}>")
|
|
568
|
+
tmp_log.debug(f"start offset={offset}")
|
|
569
|
+
|
|
570
|
+
# put dummy char to avoid Internal Server Error
|
|
571
|
+
return_string = " "
|
|
572
|
+
try:
|
|
573
|
+
# stdout name
|
|
574
|
+
full_log_name = f"{panda_config.cache_dir}/{logName.split('/')[-1]}"
|
|
575
|
+
|
|
576
|
+
# read at offset of the file
|
|
577
|
+
with open(full_log_name, "r") as file_object:
|
|
578
|
+
file_object.seek(int(offset))
|
|
579
|
+
return_string += file_object.read()
|
|
580
|
+
|
|
581
|
+
except Exception:
|
|
582
|
+
error_type, error_value, _ = sys.exc_info()
|
|
583
|
+
tmp_log.error(f"{error_type} {error_value}")
|
|
584
|
+
|
|
585
|
+
tmp_log.debug(f"end read={len(return_string)}")
|
|
586
|
+
return return_string
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def getVomsAttr(panda_request: PandaRequest) -> str:
|
|
590
|
+
"""
|
|
591
|
+
Get the VOMS attributes in sorted order.
|
|
592
|
+
Args:
|
|
593
|
+
panda_request (PandaRequest): PanDA request object.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
string: String with the VOMS attributes
|
|
597
|
+
"""
|
|
598
|
+
attributes = []
|
|
599
|
+
|
|
600
|
+
# Iterate over all the environment variables, keep only the ones related to GRST credentials (GRST: Grid Security Technology)
|
|
601
|
+
for tmp_key in panda_request.subprocess_env:
|
|
602
|
+
tmp_val = panda_request.subprocess_env[tmp_key]
|
|
603
|
+
|
|
604
|
+
# compact credentials
|
|
605
|
+
if tmp_key.startswith("GRST_CRED_"):
|
|
606
|
+
attributes.append(f"{tmp_key} : {tmp_val}\n")
|
|
607
|
+
|
|
608
|
+
return "".join(sorted(attributes))
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def getAttr(panda_request: PandaRequest, **kv: dict) -> str:
|
|
612
|
+
"""
|
|
613
|
+
Get all parameters and environment variables from the environment.
|
|
614
|
+
Args:
|
|
615
|
+
panda_request (PandaRequest): PanDA request object.
|
|
616
|
+
kv (dict): dictionary with key-value pairs
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
string: String with the attributes
|
|
620
|
+
"""
|
|
621
|
+
# add the parameters
|
|
622
|
+
return_string = "===== param =====\n"
|
|
623
|
+
for tmp_key in sorted(kv.keys()):
|
|
624
|
+
tmp_val = kv[tmp_key]
|
|
625
|
+
return_string += f"{tmp_key} = {tmp_val}\n"
|
|
626
|
+
|
|
627
|
+
# add the environment variables
|
|
628
|
+
attributes = []
|
|
629
|
+
for tmp_key in panda_request.subprocess_env:
|
|
630
|
+
tmp_val = panda_request.subprocess_env[tmp_key]
|
|
631
|
+
attributes.append(f"{tmp_key} : {tmp_val}\n")
|
|
632
|
+
|
|
633
|
+
return_string += "\n====== env ======\n"
|
|
634
|
+
attributes.sort()
|
|
635
|
+
for attribute in sorted(attributes):
|
|
636
|
+
return_string += attribute
|
|
637
|
+
|
|
638
|
+
return return_string
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def uploadLog(panda_request: PandaRequest, file: FileStorage) -> str:
|
|
642
|
+
"""
|
|
643
|
+
Upload a JEDI log file
|
|
644
|
+
Args:
|
|
645
|
+
panda_request (PandaRequest): PanDA request object.
|
|
646
|
+
file (FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
string: String with the URL to the file
|
|
650
|
+
"""
|
|
651
|
+
|
|
652
|
+
if not Protocol.isSecure(panda_request):
|
|
653
|
+
return ERROR_NOT_SECURE
|
|
654
|
+
if "/CN=limited proxy" in panda_request.subprocess_env["SSL_CLIENT_S_DN"]:
|
|
655
|
+
return ERROR_LIMITED_PROXY
|
|
656
|
+
|
|
657
|
+
tmp_log = LogWrapper(_logger, f"uploadLog <{file.filename}>")
|
|
658
|
+
tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
659
|
+
|
|
660
|
+
# get file size
|
|
661
|
+
content_length = 0
|
|
662
|
+
try:
|
|
663
|
+
content_length = int(panda_request.headers_in["content-length"])
|
|
664
|
+
except Exception:
|
|
665
|
+
if "content-length" in panda_request.headers_in:
|
|
666
|
+
tmp_log.error(f"cannot get CL : {panda_request.headers_in['content-length']}")
|
|
667
|
+
else:
|
|
668
|
+
tmp_log.error("no CL")
|
|
669
|
+
tmp_log.debug(f"size {content_length}")
|
|
670
|
+
|
|
671
|
+
# check against the size limit for logs
|
|
672
|
+
if content_length > LOG_LIMIT:
|
|
673
|
+
error_message = ERROR_SIZE_LIMIT
|
|
674
|
+
tmp_log.error(error_message)
|
|
675
|
+
tmp_log.debug("end")
|
|
676
|
+
return error_message
|
|
677
|
+
|
|
678
|
+
jedi_log_directory = "/jedilog"
|
|
679
|
+
try:
|
|
680
|
+
file_base_name = file.filename.split("/")[-1]
|
|
681
|
+
full_path = f"{panda_config.cache_dir}{jedi_log_directory}/{file_base_name}"
|
|
682
|
+
|
|
683
|
+
# delete old file
|
|
684
|
+
if os.path.exists(full_path):
|
|
685
|
+
os.remove(full_path)
|
|
686
|
+
|
|
687
|
+
# write the new file
|
|
688
|
+
with open(full_path, "wb") as file_object:
|
|
689
|
+
file_content = file.read()
|
|
690
|
+
file_object.write(file_content)
|
|
691
|
+
tmp_log.debug(f"written to {full_path}")
|
|
692
|
+
|
|
693
|
+
# return the URL depending on the protocol
|
|
694
|
+
if panda_config.disableHTTP:
|
|
695
|
+
protocol = "https"
|
|
696
|
+
server = getServer(None)
|
|
697
|
+
else:
|
|
698
|
+
protocol = "http"
|
|
699
|
+
server = getServerHTTP(None)
|
|
700
|
+
return_string = f"{protocol}://{server}/cache{jedi_log_directory}/{file_base_name}"
|
|
701
|
+
|
|
702
|
+
except Exception:
|
|
703
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
704
|
+
error_message = f"failed to write log with {error_type.__name__}:{error_value}"
|
|
705
|
+
tmp_log.error(error_message)
|
|
706
|
+
tmp_log.debug("end")
|
|
707
|
+
return error_message
|
|
708
|
+
|
|
709
|
+
tmp_log.debug("end")
|
|
710
|
+
return return_string
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def create_shards(input_list: list, size: int) -> Generator:
|
|
714
|
+
"""
|
|
715
|
+
Partitions input into shards of a given size for bulk operations.
|
|
716
|
+
@author: Miguel Branco in DQ2 Site Services code
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
input_list (list): list to be partitioned
|
|
720
|
+
size (int): size of the shards
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
list: list of shards
|
|
724
|
+
|
|
725
|
+
"""
|
|
726
|
+
shard, i = [], 0
|
|
727
|
+
for element in input_list:
|
|
728
|
+
shard.append(element)
|
|
729
|
+
i += 1
|
|
730
|
+
if i == size:
|
|
731
|
+
yield shard
|
|
732
|
+
shard, i = [], 0
|
|
733
|
+
|
|
734
|
+
if i > 0:
|
|
735
|
+
yield shard
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def get_checkpoint_filename(task_id: str, sub_id: str) -> str:
|
|
739
|
+
"""
|
|
740
|
+
Get the checkpoint file name.
|
|
741
|
+
|
|
742
|
+
Args:
|
|
743
|
+
task_id (str): task ID.
|
|
744
|
+
sub_id (str): sub ID.
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
string: checkpoint file name.
|
|
748
|
+
"""
|
|
749
|
+
return f"hpo_cp_{task_id}_{sub_id}"
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
def put_checkpoint(panda_request: PandaRequest, file: FileStorage) -> str:
|
|
753
|
+
"""
|
|
754
|
+
Upload a HPO checkpoint file to the server.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
panda_request (PandaRequest): PanDA request object.
|
|
758
|
+
file (FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
759
|
+
|
|
760
|
+
Returns:
|
|
761
|
+
string: json formatted string with status and message.
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
tmp_log = LogWrapper(_logger, f"put_checkpoint <jediTaskID_subID={file.filename}>")
|
|
765
|
+
|
|
766
|
+
# operation status, will be set to True if successful
|
|
767
|
+
status = False
|
|
768
|
+
|
|
769
|
+
if not Protocol.isSecure(panda_request):
|
|
770
|
+
error_message = "insecure request"
|
|
771
|
+
tmp_log.error(error_message)
|
|
772
|
+
return json.dumps({"status": status, "message": error_message})
|
|
773
|
+
|
|
774
|
+
tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
775
|
+
|
|
776
|
+
# extract task ID and sub ID
|
|
777
|
+
try:
|
|
778
|
+
task_id, sub_id = file.filename.split("/")[-1].split("_")
|
|
779
|
+
except Exception:
|
|
780
|
+
error_message = "failed to extract ID"
|
|
781
|
+
tmp_log.error(error_message)
|
|
782
|
+
return json.dumps({"status": status, "message": error_message})
|
|
783
|
+
|
|
784
|
+
# get the file size
|
|
785
|
+
try:
|
|
786
|
+
content_length = int(panda_request.headers_in["content-length"])
|
|
787
|
+
except Exception as exc:
|
|
788
|
+
error_message = f"cannot get int(content-length) due to {str(exc)}"
|
|
789
|
+
tmp_log.error(error_message)
|
|
790
|
+
return json.dumps({"status": status, "message": error_message})
|
|
791
|
+
tmp_log.debug(f"size {content_length}")
|
|
792
|
+
|
|
793
|
+
# compare the size against the limit for checkpoints
|
|
794
|
+
if content_length > CHECKPOINT_LIMIT:
|
|
795
|
+
error_message = f"exceeded size limit {content_length}>{CHECKPOINT_LIMIT}"
|
|
796
|
+
tmp_log.error(error_message)
|
|
797
|
+
return json.dumps({"status": status, "message": error_message})
|
|
798
|
+
|
|
799
|
+
# write the file to the cache directory
|
|
800
|
+
try:
|
|
801
|
+
full_path = os.path.join(panda_config.cache_dir, get_checkpoint_filename(task_id, sub_id))
|
|
802
|
+
# write
|
|
803
|
+
with open(full_path, "wb") as file_object:
|
|
804
|
+
file_object.write(file.read())
|
|
805
|
+
except Exception as exc:
|
|
806
|
+
error_message = f"cannot write file due to {str(exc)}"
|
|
807
|
+
tmp_log.error(error_message)
|
|
808
|
+
return json.dumps({"status": status, "message": error_message})
|
|
809
|
+
|
|
810
|
+
status = True
|
|
811
|
+
success_message = f"successfully placed at {full_path}"
|
|
812
|
+
tmp_log.debug(success_message)
|
|
813
|
+
return json.dumps({"status": status, "message": success_message})
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def delete_checkpoint(panda_request: PandaRequest, task_id: str, sub_id: str) -> str:
|
|
817
|
+
"""
|
|
818
|
+
Delete a HPO checkpoint file from the server.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
panda_request (PandaRequest): PanDA request object.
|
|
822
|
+
task_id (str): task ID.
|
|
823
|
+
sub_id (str): sub ID.
|
|
824
|
+
|
|
825
|
+
Returns:
|
|
826
|
+
string: json formatted string with status and message.
|
|
827
|
+
"""
|
|
828
|
+
|
|
829
|
+
tmp_log = LogWrapper(_logger, f"delete_checkpoint <jediTaskID={task_id} ID={sub_id}>")
|
|
830
|
+
|
|
831
|
+
if not Protocol.isSecure(panda_request):
|
|
832
|
+
tmp_log.error(ERROR_NOT_SECURE)
|
|
833
|
+
return json.dumps({"status": False, "message": ERROR_NOT_SECURE})
|
|
834
|
+
|
|
835
|
+
tmp_log.debug(f"start {panda_request.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
836
|
+
# operation status
|
|
837
|
+
status = True
|
|
838
|
+
try:
|
|
839
|
+
full_path = os.path.join(panda_config.cache_dir, get_checkpoint_filename(task_id, sub_id))
|
|
840
|
+
os.remove(full_path)
|
|
841
|
+
message = "done"
|
|
842
|
+
tmp_log.debug(message)
|
|
843
|
+
except Exception as exc:
|
|
844
|
+
message = f"failed to delete file due to {str(exc)}"
|
|
845
|
+
tmp_log.error(message)
|
|
846
|
+
status = False
|
|
847
|
+
|
|
848
|
+
return json.dumps({"status": status, "message": message})
|