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,942 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import gc
|
|
3
|
+
import gzip
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import struct
|
|
8
|
+
import sys
|
|
9
|
+
import traceback
|
|
10
|
+
import uuid
|
|
11
|
+
import zlib
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List
|
|
14
|
+
|
|
15
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
16
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
17
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
18
|
+
from werkzeug.datastructures import FileStorage
|
|
19
|
+
|
|
20
|
+
from pandaserver.api.v1.common import (
|
|
21
|
+
generate_response,
|
|
22
|
+
get_dn,
|
|
23
|
+
get_endpoint,
|
|
24
|
+
has_production_role,
|
|
25
|
+
request_validation,
|
|
26
|
+
)
|
|
27
|
+
from pandaserver.config import panda_config
|
|
28
|
+
from pandaserver.jobdispatcher import Protocol
|
|
29
|
+
from pandaserver.srvcore import CoreUtils
|
|
30
|
+
from pandaserver.srvcore.panda_request import PandaRequest
|
|
31
|
+
from pandaserver.taskbuffer.TaskBuffer import TaskBuffer
|
|
32
|
+
from pandaserver.userinterface import Client
|
|
33
|
+
|
|
34
|
+
_logger = PandaLogger().getLogger("api_file_server")
|
|
35
|
+
|
|
36
|
+
# Skip registration for files with these suffixes
|
|
37
|
+
IGNORED_SUFFIX = [".out"]
|
|
38
|
+
|
|
39
|
+
# File size limits
|
|
40
|
+
MB = 1024 * 1024
|
|
41
|
+
EVENT_PICKING_LIMIT = 10 * MB
|
|
42
|
+
LOG_LIMIT = 100 * MB
|
|
43
|
+
CHECKPOINT_LIMIT = 500 * MB
|
|
44
|
+
SANDBOX_NO_BUILD_LIMIT = 10 * MB
|
|
45
|
+
SANDBOX_LIMIT = 768 * MB
|
|
46
|
+
|
|
47
|
+
# Error messages
|
|
48
|
+
ERROR_NOT_SECURE = "ERROR : no HTTPS"
|
|
49
|
+
ERROR_LIMITED_PROXY = "ERROR: rejected due to the usage of limited proxy"
|
|
50
|
+
ERROR_OVERWRITE = "ERROR: cannot overwrite file"
|
|
51
|
+
ERROR_WRITE = "ERROR: cannot write file"
|
|
52
|
+
ERROR_SIZE_LIMIT = "ERROR: upload failure. Exceeded size limit"
|
|
53
|
+
|
|
54
|
+
global_task_buffer = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def init_task_buffer(task_buffer: TaskBuffer) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Initialize the task buffer. This method needs to be called before any other method with DB access in this module.
|
|
60
|
+
"""
|
|
61
|
+
global global_task_buffer
|
|
62
|
+
global_task_buffer = task_buffer
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _get_content_length(req: PandaRequest, tmp_logger: LogWrapper) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Get the content length of the request.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
71
|
+
tmp_logger(LogWrapper): logger object of the calling function.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
int: content length of the request.
|
|
75
|
+
"""
|
|
76
|
+
content_length = 0
|
|
77
|
+
try:
|
|
78
|
+
content_length = int(req.headers_in["content-length"])
|
|
79
|
+
except Exception:
|
|
80
|
+
if "content-length" in req.headers_in:
|
|
81
|
+
tmp_logger.error(f"Cannot get content_length: {req.headers_in['content-length']}")
|
|
82
|
+
else:
|
|
83
|
+
tmp_logger.error("No content_length for {method_name}")
|
|
84
|
+
|
|
85
|
+
tmp_logger.debug(f"Size: {content_length}")
|
|
86
|
+
return content_length
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
90
|
+
def upload_jedi_log(req: PandaRequest, file: FileStorage) -> Dict:
|
|
91
|
+
"""
|
|
92
|
+
Upload a JEDI log file
|
|
93
|
+
|
|
94
|
+
Uploads a JEDI log file and returns the URL to the file. If there is already a log file for the task, it will be overwritten. Requires a secure connection and production role.
|
|
95
|
+
|
|
96
|
+
API details:
|
|
97
|
+
HTTP Method: POST
|
|
98
|
+
Path: /v1/file_server/upload_jedi_log
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
102
|
+
file(FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict: The system response `{"success": success, "message": message, "data": data}`.
|
|
106
|
+
When successful, the data field will contain the URL to the file. Otherwise the message field will indicate the issue.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
tmp_logger = LogWrapper(_logger, f"upload_jedi_log <{file.filename}>")
|
|
110
|
+
tmp_logger.debug(f"start {req.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
111
|
+
|
|
112
|
+
# get file size
|
|
113
|
+
content_length = _get_content_length(req, tmp_logger)
|
|
114
|
+
|
|
115
|
+
# check against the size limit for logs
|
|
116
|
+
if content_length > LOG_LIMIT:
|
|
117
|
+
error_message = ERROR_SIZE_LIMIT
|
|
118
|
+
tmp_logger.error(error_message)
|
|
119
|
+
tmp_logger.debug("Done")
|
|
120
|
+
return generate_response(False, error_message)
|
|
121
|
+
|
|
122
|
+
jedi_log_directory = "/jedilog"
|
|
123
|
+
try:
|
|
124
|
+
file_base_name = file.filename.split("/")[-1]
|
|
125
|
+
full_path = f"{panda_config.cache_dir}{jedi_log_directory}/{file_base_name}"
|
|
126
|
+
|
|
127
|
+
# delete old file
|
|
128
|
+
if os.path.exists(full_path):
|
|
129
|
+
os.remove(full_path)
|
|
130
|
+
|
|
131
|
+
# write the new file
|
|
132
|
+
with open(full_path, "wb") as file_object:
|
|
133
|
+
file_content = file.read()
|
|
134
|
+
file_object.write(file_content)
|
|
135
|
+
tmp_logger.debug(f"written to {full_path}")
|
|
136
|
+
|
|
137
|
+
# return the URL depending on the protocol
|
|
138
|
+
if panda_config.disableHTTP:
|
|
139
|
+
protocol = "https"
|
|
140
|
+
else:
|
|
141
|
+
protocol = "http"
|
|
142
|
+
success, server = get_endpoint(protocol)
|
|
143
|
+
if not success:
|
|
144
|
+
error_message = f"cannot get endpoint: {server}"
|
|
145
|
+
tmp_logger.error(error_message)
|
|
146
|
+
tmp_logger.debug("Done")
|
|
147
|
+
return generate_response(False, error_message)
|
|
148
|
+
|
|
149
|
+
file_url = f"{protocol}://{server}/cache{jedi_log_directory}/{file_base_name}"
|
|
150
|
+
tmp_logger.debug("Done")
|
|
151
|
+
return generate_response(True, data=file_url)
|
|
152
|
+
|
|
153
|
+
except Exception:
|
|
154
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
155
|
+
error_message = f"failed to write log with {error_type.__name__}:{error_value}"
|
|
156
|
+
tmp_logger.error(error_message)
|
|
157
|
+
tmp_logger.debug("Done")
|
|
158
|
+
return generate_response(False, error_message)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
162
|
+
def update_jedi_log(req: PandaRequest, file: FileStorage) -> Dict:
|
|
163
|
+
"""
|
|
164
|
+
Update a JEDI log file
|
|
165
|
+
|
|
166
|
+
Updates a JEDI log file, appending more content at the end of the file. Requires a secure connection and production role.
|
|
167
|
+
|
|
168
|
+
API details:
|
|
169
|
+
HTTP Method: POST
|
|
170
|
+
Path: /v1/file_server/update_jedi_log
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
174
|
+
file(FileStorage): werkzeug.FileStorage object to be updated.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
tmp_logger = LogWrapper(_logger, f"update_jedi_log < {file.filename} >")
|
|
181
|
+
tmp_logger.debug("Start")
|
|
182
|
+
|
|
183
|
+
# write to file
|
|
184
|
+
try:
|
|
185
|
+
# expand
|
|
186
|
+
new_content = zlib.decompress(file.read())
|
|
187
|
+
|
|
188
|
+
# stdout name
|
|
189
|
+
log_name = f"{panda_config.cache_dir}/{file.filename.split('/')[-1]}"
|
|
190
|
+
|
|
191
|
+
# append to file end
|
|
192
|
+
with open(log_name, "a") as file_object:
|
|
193
|
+
file_object.write(new_content)
|
|
194
|
+
|
|
195
|
+
except Exception:
|
|
196
|
+
error_type, error_value, _ = sys.exc_info()
|
|
197
|
+
tmp_logger.error(f"{error_type} {error_value}")
|
|
198
|
+
return generate_response(False, f"ERROR: cannot update file with {error_type} {error_value}")
|
|
199
|
+
|
|
200
|
+
tmp_logger.debug("Done")
|
|
201
|
+
return generate_response(True)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@request_validation(_logger, request_method="GET")
|
|
205
|
+
def download_jedi_log(req: PandaRequest, log_name: str, offset: int = 0) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Download JEDI log file
|
|
208
|
+
|
|
209
|
+
Downloads the JEDI log file, if required at a particular offset.
|
|
210
|
+
|
|
211
|
+
API details:
|
|
212
|
+
HTTP Method: POST
|
|
213
|
+
Path: /v1/file_server/download_jedi_log
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
217
|
+
log_name(string): log file name
|
|
218
|
+
offset(int): offset in the file
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
str: The content of the log file or an error message.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
tmp_logger = LogWrapper(_logger, f"download_jedi_log <{log_name}>")
|
|
225
|
+
tmp_logger.debug(f"Start offset={offset}")
|
|
226
|
+
|
|
227
|
+
# put dummy char to avoid Internal Server Error
|
|
228
|
+
return_string = " "
|
|
229
|
+
try:
|
|
230
|
+
# stdout name
|
|
231
|
+
full_log_name = f"{panda_config.cache_dir}/{log_name.split('/')[-1]}"
|
|
232
|
+
|
|
233
|
+
# read at offset of the file
|
|
234
|
+
with open(full_log_name, "r") as file_object:
|
|
235
|
+
file_object.seek(int(offset))
|
|
236
|
+
return_string += file_object.read()
|
|
237
|
+
|
|
238
|
+
except Exception:
|
|
239
|
+
error_type, error_value, _ = sys.exc_info()
|
|
240
|
+
tmp_logger.error(f"Failed with: {error_type} {error_value}")
|
|
241
|
+
|
|
242
|
+
tmp_logger.debug(f"Read {len(return_string)} bytes")
|
|
243
|
+
tmp_logger.debug("Done")
|
|
244
|
+
return return_string
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
248
|
+
def upload_cache_file(req: PandaRequest, file: FileStorage) -> Dict:
|
|
249
|
+
"""
|
|
250
|
+
Upload a cache file
|
|
251
|
+
|
|
252
|
+
Uploads a file to the cache. When not touched, cache files are expired after some time.
|
|
253
|
+
User caches will get registered in the PanDA database and will account towards user limits.
|
|
254
|
+
PanDA log files will be stored in gzip format. Requires a secure connection.
|
|
255
|
+
|
|
256
|
+
API details:
|
|
257
|
+
HTTP Method: POST
|
|
258
|
+
Path: /v1/file_server/upload_cache_file
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
262
|
+
file(FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
tmp_logger = LogWrapper(_logger, f"upload_cache_file-{naive_utcnow().isoformat('/')}")
|
|
269
|
+
tmp_logger.debug(f"Start")
|
|
270
|
+
|
|
271
|
+
# check if using secure connection and the proxy is not limited
|
|
272
|
+
# we run these checks explicitly to trigger garbage collection
|
|
273
|
+
if not Protocol.isSecure(req):
|
|
274
|
+
tmp_logger.error("No HTTPS. Triggering garbage collection...")
|
|
275
|
+
gc.collect()
|
|
276
|
+
tmp_logger.debug("Done")
|
|
277
|
+
return generate_response(False, ERROR_NOT_SECURE)
|
|
278
|
+
|
|
279
|
+
if "/CN=limited proxy" in req.subprocess_env["SSL_CLIENT_S_DN"]:
|
|
280
|
+
tmp_logger.error("Limited proxy is used. Triggering garbage collection...")
|
|
281
|
+
gc.collect()
|
|
282
|
+
tmp_logger.debug("Done")
|
|
283
|
+
return generate_response(False, ERROR_LIMITED_PROXY)
|
|
284
|
+
|
|
285
|
+
# user name
|
|
286
|
+
user_name = CoreUtils.clean_user_id(req.subprocess_env["SSL_CLIENT_S_DN"])
|
|
287
|
+
tmp_logger.debug(f"user_name={user_name} file_path={file.filename}")
|
|
288
|
+
|
|
289
|
+
# get file size limit
|
|
290
|
+
# log file
|
|
291
|
+
if any(file.filename.endswith(suffix) for suffix in IGNORED_SUFFIX):
|
|
292
|
+
no_build = False
|
|
293
|
+
size_limit = LOG_LIMIT
|
|
294
|
+
# no build case
|
|
295
|
+
elif not file.filename.startswith("sources."):
|
|
296
|
+
no_build = True
|
|
297
|
+
size_limit = SANDBOX_NO_BUILD_LIMIT
|
|
298
|
+
# general sandbox limit
|
|
299
|
+
else:
|
|
300
|
+
no_build = False
|
|
301
|
+
size_limit = SANDBOX_LIMIT
|
|
302
|
+
|
|
303
|
+
# get actual file size
|
|
304
|
+
content_length = _get_content_length(req, tmp_logger)
|
|
305
|
+
|
|
306
|
+
# check if we are above the size limit
|
|
307
|
+
if content_length > size_limit:
|
|
308
|
+
error_message = f"{ERROR_SIZE_LIMIT} {content_length // MB} MB > {size_limit // MB} MB."
|
|
309
|
+
if no_build:
|
|
310
|
+
error_message += " Please submit the job without --noBuild/--libDS since those options impose a tighter size limit"
|
|
311
|
+
else:
|
|
312
|
+
error_message += " Please remove redundant files from your work area"
|
|
313
|
+
tmp_logger.error(error_message)
|
|
314
|
+
tmp_logger.debug("Triggering garbage collection...")
|
|
315
|
+
gc.collect()
|
|
316
|
+
tmp_logger.debug("Done")
|
|
317
|
+
return generate_response(False, error_message)
|
|
318
|
+
|
|
319
|
+
# write to file
|
|
320
|
+
try:
|
|
321
|
+
file_name = file.filename.split("/")[-1]
|
|
322
|
+
full_path = f"{panda_config.cache_dir}/{file_name}"
|
|
323
|
+
|
|
324
|
+
# avoid overwriting
|
|
325
|
+
if os.path.exists(full_path) and file.filename.split(".")[-1] != "__ow__":
|
|
326
|
+
# touch
|
|
327
|
+
os.utime(full_path, None)
|
|
328
|
+
# send error message
|
|
329
|
+
error_message = ERROR_OVERWRITE
|
|
330
|
+
tmp_logger.debug(f"{ERROR_OVERWRITE} {file_name}")
|
|
331
|
+
tmp_logger.debug("end")
|
|
332
|
+
return generate_response(False, error_message)
|
|
333
|
+
|
|
334
|
+
# write the file to the cache directory
|
|
335
|
+
with open(full_path, "wb") as file_object:
|
|
336
|
+
file_content = file.read()
|
|
337
|
+
if hasattr(panda_config, "compress_file_names") and [
|
|
338
|
+
True for patt in panda_config.compress_file_names.split(",") if re.search(patt, file_name) is not None
|
|
339
|
+
]:
|
|
340
|
+
file_content = gzip.compress(file_content)
|
|
341
|
+
file_object.write(file_content)
|
|
342
|
+
|
|
343
|
+
except Exception:
|
|
344
|
+
error_message = ERROR_WRITE
|
|
345
|
+
tmp_logger.error(error_message)
|
|
346
|
+
tmp_logger.debug("Triggering garbage collection...")
|
|
347
|
+
gc.collect()
|
|
348
|
+
tmp_logger.debug("Done")
|
|
349
|
+
return generate_response(False, error_message)
|
|
350
|
+
|
|
351
|
+
# calculate the checksum
|
|
352
|
+
try:
|
|
353
|
+
# decode Footer
|
|
354
|
+
footer = file_content[-8:]
|
|
355
|
+
checksum, _ = struct.unpack("II", footer)
|
|
356
|
+
checksum = str(checksum)
|
|
357
|
+
tmp_logger.debug(f"CRC from gzip Footer {checksum}")
|
|
358
|
+
except Exception:
|
|
359
|
+
# use None to avoid delay for now
|
|
360
|
+
checksum = None
|
|
361
|
+
tmp_logger.debug(f"No CRC calculated {checksum}")
|
|
362
|
+
|
|
363
|
+
# calculate the file size
|
|
364
|
+
file_size = len(file_content)
|
|
365
|
+
|
|
366
|
+
# log the full file information
|
|
367
|
+
tmp_logger.debug(f"Written dn={user_name}, file={full_path}, size={file_size // MB} MB, crc={checksum}")
|
|
368
|
+
|
|
369
|
+
# record the file information to DB
|
|
370
|
+
if panda_config.record_sandbox_info:
|
|
371
|
+
# ignore some suffixes, e.g. out
|
|
372
|
+
to_insert = True
|
|
373
|
+
for patt in IGNORED_SUFFIX:
|
|
374
|
+
if file.filename.endswith(patt):
|
|
375
|
+
to_insert = False
|
|
376
|
+
break
|
|
377
|
+
if not to_insert:
|
|
378
|
+
tmp_logger.debug("skipped to insert to DB")
|
|
379
|
+
else:
|
|
380
|
+
status_client, output_client = Client.register_cache_file(user_name, file.filename, file_size, checksum)
|
|
381
|
+
if status_client != 0:
|
|
382
|
+
error_message = f"ERROR : failed to register sandbox to DB with {status_client} {output_client}"
|
|
383
|
+
tmp_logger.error(error_message)
|
|
384
|
+
tmp_logger.debug("Done")
|
|
385
|
+
return generate_response(False, error_message)
|
|
386
|
+
|
|
387
|
+
success = output_client["success"]
|
|
388
|
+
message = output_client["message"]
|
|
389
|
+
if not success:
|
|
390
|
+
error_message = f"ERROR : failed to register sandbox to DB with {message}"
|
|
391
|
+
tmp_logger.error(error_message)
|
|
392
|
+
tmp_logger.debug("Done")
|
|
393
|
+
return generate_response(False, error_message)
|
|
394
|
+
|
|
395
|
+
tmp_logger.debug(f"Registered file in database with: {output_client}")
|
|
396
|
+
|
|
397
|
+
tmp_logger.debug("Triggering garbage collection...")
|
|
398
|
+
gc.collect()
|
|
399
|
+
tmp_logger.debug("Done")
|
|
400
|
+
|
|
401
|
+
return generate_response(True)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
405
|
+
def touch_cache_file(req: PandaRequest, file_name: str) -> Dict:
|
|
406
|
+
"""
|
|
407
|
+
Touch file in the cache directory.
|
|
408
|
+
|
|
409
|
+
Touches a file in the cache directory. It avoids the file to expire and being deleted by server clean up processes. Requires a secure connection.
|
|
410
|
+
|
|
411
|
+
API details:
|
|
412
|
+
HTTP Method: POST
|
|
413
|
+
Path: /v1/file_server/touch_cache_file
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
417
|
+
file_name(string): file name to be deleted
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
tmp_logger = LogWrapper(_logger, f"touch_cache_file < {file_name} >")
|
|
424
|
+
tmp_logger.debug(f"Start")
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
os.utime(f"{panda_config.cache_dir}/{file_name.split('/')[-1]}", None)
|
|
428
|
+
tmp_logger.debug(f"Done")
|
|
429
|
+
return generate_response(True)
|
|
430
|
+
except Exception:
|
|
431
|
+
error_type, error_value = sys.exc_info()[:2]
|
|
432
|
+
message = f"Failed to touch file with: {error_type} {error_value}"
|
|
433
|
+
_logger.error(message)
|
|
434
|
+
return generate_response(False, message)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
438
|
+
def delete_cache_file(req: PandaRequest, file_name: str) -> Dict:
|
|
439
|
+
"""
|
|
440
|
+
Delete cache file
|
|
441
|
+
|
|
442
|
+
Deletes a file from the cache directory. Currently a dummy method. Requires a secure connection.
|
|
443
|
+
|
|
444
|
+
API details:
|
|
445
|
+
HTTP Method: POST
|
|
446
|
+
Path: /v1/file_server/delete_cache_file
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
450
|
+
file_name(string): file name to be deleted
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
dict: The system response `{"success": success, "message": message, "data": data}`.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
tmp_logger = LogWrapper(_logger, f"delete_cache_file <{file_name}>")
|
|
457
|
+
tmp_logger.debug(f"Start")
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
# may be reused for re-brokerage
|
|
461
|
+
# os.remove('%s/%s' % (panda_config.cache_dir, file_name.split('/')[-1]))
|
|
462
|
+
return generate_response(True)
|
|
463
|
+
except Exception:
|
|
464
|
+
return generate_response(False)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@request_validation(_logger, secure=True, production=True, request_method="POST")
|
|
468
|
+
def register_cache_file(req: PandaRequest, user_name: str, file_name: str, file_size: int, checksum: str) -> Dict:
|
|
469
|
+
"""
|
|
470
|
+
Register cache file
|
|
471
|
+
|
|
472
|
+
Registers a file from the cache directory into the PanDA database, so that PanDA knows the server it's on. Requires a secure connection and production role.
|
|
473
|
+
|
|
474
|
+
API details:
|
|
475
|
+
HTTP Method: POST
|
|
476
|
+
Path: /v1/file_server/register_cache_file
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
480
|
+
user_name(string): user that uploaded the file
|
|
481
|
+
file_name(string): file name
|
|
482
|
+
file_size(int): file size
|
|
483
|
+
checksum(string): checksum
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
tmp_logger = LogWrapper(_logger, f"register_cache_file {user_name} {file_name}")
|
|
490
|
+
tmp_logger.debug("Start")
|
|
491
|
+
|
|
492
|
+
# the files are on a particular server and not accessible through the LB endpoint
|
|
493
|
+
# therefore we need to register a preconfigured hostname or hostname of the caller
|
|
494
|
+
if hasattr(panda_config, "sandboxHostname") and panda_config.sandboxHostname:
|
|
495
|
+
host_name = panda_config.sandboxHostname
|
|
496
|
+
else:
|
|
497
|
+
host_name = req.get_remote_host()
|
|
498
|
+
|
|
499
|
+
message = global_task_buffer.insertSandboxFileInfo(user_name, host_name, file_name, file_size, checksum)
|
|
500
|
+
if message != "OK":
|
|
501
|
+
tmp_logger.debug("Done")
|
|
502
|
+
return generate_response(False, message)
|
|
503
|
+
|
|
504
|
+
tmp_logger.debug("Done")
|
|
505
|
+
return generate_response(True)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
509
|
+
def validate_cache_file(req: PandaRequest, file_size: int, checksum: int | str) -> Dict:
|
|
510
|
+
"""
|
|
511
|
+
Validate cache file
|
|
512
|
+
|
|
513
|
+
Validates a cache file owned by the caller by checking the file metadata that was registered in the database. Requires a secure connection.
|
|
514
|
+
|
|
515
|
+
API details:
|
|
516
|
+
HTTP Method: POST
|
|
517
|
+
Path: /v1/file_server/validate_cache_file
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
521
|
+
file_size(int): file size
|
|
522
|
+
checksum(int): checksum
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When successful the message will return the host and file name.
|
|
526
|
+
When unsuccessful, the message field will indicate the issue.
|
|
527
|
+
"""
|
|
528
|
+
user = get_dn(req)
|
|
529
|
+
message = global_task_buffer.checkSandboxFile(user, file_size, checksum)
|
|
530
|
+
|
|
531
|
+
# The file was not found or there was an exception
|
|
532
|
+
if message and not message.startswith("FOUND"):
|
|
533
|
+
return generate_response(False, message)
|
|
534
|
+
|
|
535
|
+
# The file was found
|
|
536
|
+
return generate_response(True, message)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _get_checkpoint_filename(task_id: str, sub_id: str) -> Dict:
|
|
540
|
+
"""
|
|
541
|
+
Get the checkpoint file name.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
task_id(string): task ID.
|
|
545
|
+
sub_id(string): sub ID.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
string: checkpoint file name.
|
|
549
|
+
"""
|
|
550
|
+
return f"hpo_cp_{task_id}_{sub_id}"
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
554
|
+
def upload_hpo_checkpoint(req: PandaRequest, file: FileStorage) -> Dict:
|
|
555
|
+
"""
|
|
556
|
+
Upload a HPO checkpoint file
|
|
557
|
+
|
|
558
|
+
Uploads a HPO checkpoint file to the server. Requires a secure connection.
|
|
559
|
+
|
|
560
|
+
API details:
|
|
561
|
+
HTTP Method: POST
|
|
562
|
+
Path: /v1/file_server/upload_hpo_checkpoint
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
566
|
+
file(FileStorage): werkzeug.FileStorage object to be uploaded.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
570
|
+
"""
|
|
571
|
+
|
|
572
|
+
tmp_logger = LogWrapper(_logger, f"upload_hpo_checkpoint <jediTaskID_subID={file.filename}>")
|
|
573
|
+
|
|
574
|
+
tmp_logger.debug(f"Start {req.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
575
|
+
|
|
576
|
+
# extract task ID and sub ID
|
|
577
|
+
try:
|
|
578
|
+
task_id, sub_id = file.filename.split("/")[-1].split("_")
|
|
579
|
+
except Exception:
|
|
580
|
+
error_message = "Failed to extract task and sub IDs"
|
|
581
|
+
tmp_logger.error(error_message)
|
|
582
|
+
return generate_response(False, error_message)
|
|
583
|
+
|
|
584
|
+
# get the file size
|
|
585
|
+
content_length = _get_content_length(req, tmp_logger)
|
|
586
|
+
if not content_length:
|
|
587
|
+
error_message = f"Cannot get content-length"
|
|
588
|
+
tmp_logger.error(error_message)
|
|
589
|
+
return generate_response(False, error_message)
|
|
590
|
+
|
|
591
|
+
# compare the size against the limit for checkpoints
|
|
592
|
+
if content_length > CHECKPOINT_LIMIT:
|
|
593
|
+
error_message = f"Exceeded size limit {content_length}>{CHECKPOINT_LIMIT}"
|
|
594
|
+
tmp_logger.error(error_message)
|
|
595
|
+
return generate_response(False, error_message)
|
|
596
|
+
|
|
597
|
+
# write the file to the cache directory
|
|
598
|
+
try:
|
|
599
|
+
full_path = os.path.join(panda_config.cache_dir, _get_checkpoint_filename(task_id, sub_id))
|
|
600
|
+
# write
|
|
601
|
+
with open(full_path, "wb") as file_object:
|
|
602
|
+
file_object.write(file.read())
|
|
603
|
+
except Exception as exc:
|
|
604
|
+
error_message = f"Cannot write file due to {str(exc)}"
|
|
605
|
+
tmp_logger.error(error_message)
|
|
606
|
+
return generate_response(False, error_message)
|
|
607
|
+
|
|
608
|
+
success_message = f"Successfully placed at {full_path}"
|
|
609
|
+
tmp_logger.debug(success_message)
|
|
610
|
+
tmp_logger.debug("Done")
|
|
611
|
+
return generate_response(True, message=success_message, data=full_path)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
615
|
+
def delete_hpo_checkpoint(req: PandaRequest, task_id: str, sub_id: str) -> Dict:
|
|
616
|
+
"""
|
|
617
|
+
Delete a HPO checkpoint file.
|
|
618
|
+
|
|
619
|
+
Deletes a HPO checkpoint file from the server. Requires a secure connection.
|
|
620
|
+
|
|
621
|
+
API details:
|
|
622
|
+
HTTP Method: POST
|
|
623
|
+
Path: /v1/file_server/delete_hpo_checkpoint
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
627
|
+
task_id(string): JEDI task ID
|
|
628
|
+
sub_id(string): sub ID.
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
tmp_logger = LogWrapper(_logger, f"delete_hpo_checkpoint <jediTaskID={task_id} ID={sub_id}>")
|
|
635
|
+
|
|
636
|
+
tmp_logger.debug(f"Start {req.subprocess_env['SSL_CLIENT_S_DN']}")
|
|
637
|
+
|
|
638
|
+
try:
|
|
639
|
+
full_path = os.path.join(panda_config.cache_dir, _get_checkpoint_filename(task_id, sub_id))
|
|
640
|
+
os.remove(full_path)
|
|
641
|
+
tmp_logger.debug("Done")
|
|
642
|
+
return generate_response(True)
|
|
643
|
+
except Exception as exc:
|
|
644
|
+
message = f"Failed to delete file due to {str(exc)}"
|
|
645
|
+
tmp_logger.error(message)
|
|
646
|
+
return generate_response(False, message=message)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
650
|
+
def upload_file_recovery_request(
|
|
651
|
+
req: PandaRequest,
|
|
652
|
+
task_id: int = None,
|
|
653
|
+
dry_run: bool = None,
|
|
654
|
+
dataset: str = None,
|
|
655
|
+
files: List[str] = None,
|
|
656
|
+
no_child_retry: bool = False,
|
|
657
|
+
resurrect_datasets: bool = False,
|
|
658
|
+
force: bool = False,
|
|
659
|
+
reproduce_parent: bool = False,
|
|
660
|
+
reproduce_upto_nth_gen: int = 0,
|
|
661
|
+
) -> Dict:
|
|
662
|
+
"""
|
|
663
|
+
Upload file recovery request
|
|
664
|
+
|
|
665
|
+
Upload request to recover lost files. Either task_id or dataset needs to be specified. Requires a secure connection.
|
|
666
|
+
|
|
667
|
+
API details:
|
|
668
|
+
HTTP Method: POST
|
|
669
|
+
Path: /v1/file_server/upload_file_recovery_request
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
673
|
+
task_id(int, optional): JEDI task ID. Either task_id or dataset must be provided.
|
|
674
|
+
dry_run(bool, optional): dry run flag.
|
|
675
|
+
dataset(string, optional): the dataset name in which to recover files. Either task_id or dataset must be provided.
|
|
676
|
+
files(list of str, optional): list of file names to recover.
|
|
677
|
+
no_child_retry(bool, optional): flag to avoid retrying child tasks. Default is False.
|
|
678
|
+
resurrect_datasets(bool, optional): Specifies whether to resurrect datasets when they were already deleted. Default is False.
|
|
679
|
+
force(bool, optional): To force recovery even if there is no lost file. Default is False.
|
|
680
|
+
reproduce_parent(bool, optional): Specifies whether to reproduce the parent task if the input files that originally generated the lost files have been deleted. Default: False.
|
|
681
|
+
reproduce_upto_nth_gen(int, optional): Defines how many generations of parent tasks should be reproduced. Default 0, meaning no parent tasks are reproduced. When this is set to N>0, reproduce_parent is set to True automatically.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
|
|
688
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
689
|
+
|
|
690
|
+
tmp_logger = LogWrapper(_logger, f"put_file_recovery_request < task_id={task_id} >")
|
|
691
|
+
tmp_logger.debug(f"Start user={user_name}")
|
|
692
|
+
|
|
693
|
+
try:
|
|
694
|
+
# check that at least task_id or dataset is provided
|
|
695
|
+
if not task_id and not dataset:
|
|
696
|
+
error_message = "Either task_id or dataset must be provided"
|
|
697
|
+
tmp_logger.error(error_message)
|
|
698
|
+
return generate_response(False, error_message)
|
|
699
|
+
|
|
700
|
+
# generate the filename
|
|
701
|
+
file_name = f"{panda_config.cache_dir}/recov.{str(uuid.uuid4())}"
|
|
702
|
+
log_filename = f"jedilog/lost_file_recovery.{str(uuid.uuid4())}.log"
|
|
703
|
+
tmp_logger.debug(f"file={file_name}")
|
|
704
|
+
|
|
705
|
+
# check if the user has production manager role
|
|
706
|
+
is_production_manager = has_production_role(req)
|
|
707
|
+
|
|
708
|
+
# write the file content
|
|
709
|
+
|
|
710
|
+
with open(file_name, "w") as file_object:
|
|
711
|
+
data = {
|
|
712
|
+
"userName": user_name,
|
|
713
|
+
"creationTime": creation_time,
|
|
714
|
+
"logFilename": log_filename,
|
|
715
|
+
"isProductionManager": is_production_manager,
|
|
716
|
+
}
|
|
717
|
+
if task_id:
|
|
718
|
+
data["jediTaskID"] = task_id
|
|
719
|
+
if dry_run:
|
|
720
|
+
data["dryRun"] = True
|
|
721
|
+
if dataset:
|
|
722
|
+
data["ds"] = dataset
|
|
723
|
+
if files:
|
|
724
|
+
data["files"] = ",".join(files)
|
|
725
|
+
if no_child_retry:
|
|
726
|
+
data["noChildRetry"] = True
|
|
727
|
+
if resurrect_datasets:
|
|
728
|
+
data["resurrectDS"] = True
|
|
729
|
+
if force:
|
|
730
|
+
data["force"] = True
|
|
731
|
+
if reproduce_parent:
|
|
732
|
+
data["reproduceParent"] = True
|
|
733
|
+
if reproduce_upto_nth_gen > 0:
|
|
734
|
+
data["reproduceUptoNthGen"] = reproduce_upto_nth_gen
|
|
735
|
+
|
|
736
|
+
json.dump(data, file_object)
|
|
737
|
+
except Exception as exc:
|
|
738
|
+
error_message = f"cannot put request due to {str(exc)} "
|
|
739
|
+
tmp_logger.error(error_message + traceback.format_exc())
|
|
740
|
+
return generate_response(False, error_message)
|
|
741
|
+
|
|
742
|
+
# create an empty log file to be filled later
|
|
743
|
+
Path(os.path.join(panda_config.cache_dir, log_filename)).touch()
|
|
744
|
+
|
|
745
|
+
# return the URL depending on the protocol
|
|
746
|
+
protocol = "https" if panda_config.disableHTTP else "http"
|
|
747
|
+
_, server = get_endpoint(protocol)
|
|
748
|
+
log_file_url = f"{protocol}://{server}/cache/{log_filename}"
|
|
749
|
+
|
|
750
|
+
tmp_logger.debug("done")
|
|
751
|
+
data = {"logFileURL": log_file_url}
|
|
752
|
+
return generate_response(True, message="The request was accepted and will be processed in a few minutes", data=data)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
756
|
+
def upload_workflow_request(req: PandaRequest, data: str, dry_run: bool = False, sync: bool = False) -> Dict:
|
|
757
|
+
"""
|
|
758
|
+
Upload workflow request to the server.
|
|
759
|
+
|
|
760
|
+
Uploads a workflow request to the server. The request can be processed synchronously or asynchronously. Requires a secure connection.
|
|
761
|
+
|
|
762
|
+
API details:
|
|
763
|
+
HTTP Method: POST
|
|
764
|
+
Path: /v1/file_server/upload_workflow_request
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
768
|
+
data(string): workflow request data
|
|
769
|
+
dry_run(bool): requests the workflow to be executed synchronously in dry_run mode
|
|
770
|
+
sync(bool): requests the workflow to be processed synchronously
|
|
771
|
+
Returns:
|
|
772
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
773
|
+
When the request asked to process the workflow synchronously or with the check file, the data field will contain the response.
|
|
774
|
+
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
|
|
778
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
779
|
+
|
|
780
|
+
tmp_logger = LogWrapper(_logger, "upload_workflow_request")
|
|
781
|
+
|
|
782
|
+
tmp_logger.debug(f"Start user={user_name} dry_run={dry_run}")
|
|
783
|
+
|
|
784
|
+
try:
|
|
785
|
+
# Generate the filename
|
|
786
|
+
file_name = f"{panda_config.cache_dir}/workflow.{str(uuid.uuid4())}"
|
|
787
|
+
tmp_logger.debug(f"file={file_name}")
|
|
788
|
+
|
|
789
|
+
# Write out the workflow request
|
|
790
|
+
with open(file_name, "w") as file_object:
|
|
791
|
+
data_dict = {
|
|
792
|
+
"userName": user_name,
|
|
793
|
+
"creationTime": creation_time,
|
|
794
|
+
"data": json.loads(data),
|
|
795
|
+
}
|
|
796
|
+
json.dump(data_dict, file_object)
|
|
797
|
+
|
|
798
|
+
# Submitter requested synchronous processing
|
|
799
|
+
if sync or dry_run:
|
|
800
|
+
tmp_logger.debug("Starting synchronous processing of the workflow")
|
|
801
|
+
from pandaserver.taskbuffer.workflow_processor import WorkflowProcessor
|
|
802
|
+
|
|
803
|
+
processor = WorkflowProcessor(log_stream=_logger)
|
|
804
|
+
if dry_run:
|
|
805
|
+
ret = processor.process(file_name, True, True, True, True)
|
|
806
|
+
else:
|
|
807
|
+
ret = processor.process(file_name, True, False, True, False)
|
|
808
|
+
|
|
809
|
+
# Delete the file to prevent it being processed again
|
|
810
|
+
if os.path.exists(file_name):
|
|
811
|
+
try:
|
|
812
|
+
os.remove(file_name)
|
|
813
|
+
except Exception:
|
|
814
|
+
pass
|
|
815
|
+
tmp_logger.debug("Done")
|
|
816
|
+
return generate_response(True, data=ret)
|
|
817
|
+
|
|
818
|
+
except Exception as exc:
|
|
819
|
+
error_message = f"Cannot upload the workflow request: {str(exc)} "
|
|
820
|
+
tmp_logger.error(error_message + traceback.format_exc())
|
|
821
|
+
return generate_response(False, error_message)
|
|
822
|
+
|
|
823
|
+
# Submitter did not request synchronous processing
|
|
824
|
+
tmp_logger.debug("Done")
|
|
825
|
+
return generate_response(True, message="The request was accepted and will be processed in a few minutes")
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
@request_validation(_logger, secure=True, request_method="POST")
|
|
829
|
+
def upload_event_picking_request(
|
|
830
|
+
req: PandaRequest,
|
|
831
|
+
run_event_list: str = "",
|
|
832
|
+
data_type: str = "",
|
|
833
|
+
stream_name: str = "",
|
|
834
|
+
dataset_name: str = "",
|
|
835
|
+
ami_tag: str = "",
|
|
836
|
+
user_dataset_name: str = "",
|
|
837
|
+
locked_by: str = "",
|
|
838
|
+
parameters: str = "",
|
|
839
|
+
input_file_list: str = "",
|
|
840
|
+
n_sites: str = "",
|
|
841
|
+
user_task_name: str = "",
|
|
842
|
+
ei_api: str = "",
|
|
843
|
+
include_guids: bool = False,
|
|
844
|
+
) -> Dict:
|
|
845
|
+
"""
|
|
846
|
+
Upload event picking request to the server.
|
|
847
|
+
|
|
848
|
+
Uploads an event picking request to the server. Requires a secure connection.
|
|
849
|
+
|
|
850
|
+
API details:
|
|
851
|
+
HTTP Method: POST
|
|
852
|
+
Path: /v1/file_server/upload_event_picking_request
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
req(PandaRequest): internally generated request object containing the env variables
|
|
856
|
+
run_event_list(string): run and event list.
|
|
857
|
+
data_type(string): data type.
|
|
858
|
+
stream_name(string): stream name.
|
|
859
|
+
dataset_name(string): dataset name.
|
|
860
|
+
ami_tag(string): AMI tag.
|
|
861
|
+
user_dataset_name(string): user dataset name.
|
|
862
|
+
locked_by(string): locking agent.
|
|
863
|
+
parameters(string): parameters.
|
|
864
|
+
input_file_list(string): input file list.
|
|
865
|
+
n_sites(string): number of sites.
|
|
866
|
+
user_task_name(string): user task name.
|
|
867
|
+
ei_api(string): event index API.
|
|
868
|
+
include_guids(bool): flag to indicate if GUIDs are included with the run-event list
|
|
869
|
+
|
|
870
|
+
Returns:
|
|
871
|
+
dict: The system response `{"success": success, "message": message, "data": data}`. When unsuccessful, the message field will indicate the issue.
|
|
872
|
+
|
|
873
|
+
"""
|
|
874
|
+
|
|
875
|
+
user_name = req.subprocess_env["SSL_CLIENT_S_DN"]
|
|
876
|
+
|
|
877
|
+
tmp_logger = LogWrapper(_logger, f"upload_event_picking_request-{naive_utcnow().isoformat('/')}")
|
|
878
|
+
tmp_logger.debug(f"Start for {user_name}")
|
|
879
|
+
|
|
880
|
+
creation_time = naive_utcnow().strftime("%Y-%m-%d %H:%M:%S")
|
|
881
|
+
|
|
882
|
+
# get total size
|
|
883
|
+
content_length = _get_content_length(req, tmp_logger)
|
|
884
|
+
if not content_length:
|
|
885
|
+
error_message = "Cannot get content-length from HTTP request."
|
|
886
|
+
tmp_logger.error(f"{error_message}")
|
|
887
|
+
return generate_response(False, f"ERROR: {error_message}")
|
|
888
|
+
|
|
889
|
+
if content_length > EVENT_PICKING_LIMIT:
|
|
890
|
+
error_message = f"Run/event list is too large. Exceeded size limit {content_length}>{EVENT_PICKING_LIMIT}."
|
|
891
|
+
tmp_logger.error(f"{error_message} ")
|
|
892
|
+
return generate_response(False, f"ERROR: {error_message}")
|
|
893
|
+
|
|
894
|
+
try:
|
|
895
|
+
# generate the filename
|
|
896
|
+
file_name = f"{panda_config.cache_dir}/evp.{str(uuid.uuid4())}"
|
|
897
|
+
tmp_logger.debug(f"file: {file_name}")
|
|
898
|
+
|
|
899
|
+
# write the information to file
|
|
900
|
+
file_content = (
|
|
901
|
+
f"userName={user_name}\n"
|
|
902
|
+
f"creationTime={creation_time}\n"
|
|
903
|
+
f"eventPickDataType={data_type}\n"
|
|
904
|
+
f"eventPickStreamName={stream_name}\n"
|
|
905
|
+
f"eventPickDS={dataset_name}\n"
|
|
906
|
+
f"eventPickAmiTag={ami_tag}\n"
|
|
907
|
+
f"eventPickNumSites={n_sites}\n"
|
|
908
|
+
f"userTaskName={user_task_name}\n"
|
|
909
|
+
f"userDatasetName={user_dataset_name}\n"
|
|
910
|
+
f"lockedBy={locked_by}\n"
|
|
911
|
+
f"params={parameters}\n"
|
|
912
|
+
f"inputFileList={input_file_list}\n"
|
|
913
|
+
f"ei_api={ei_api}\n"
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
with open(file_name, "w") as file_object:
|
|
917
|
+
file_object.write(file_content)
|
|
918
|
+
run_event_guid_map = {}
|
|
919
|
+
|
|
920
|
+
valid_entry_length = 3 if include_guids else 2
|
|
921
|
+
|
|
922
|
+
for tmp_line in run_event_list.split("\n"):
|
|
923
|
+
tmp_items = tmp_line.split()
|
|
924
|
+
|
|
925
|
+
# Skip invalid entries
|
|
926
|
+
if len(tmp_items) != valid_entry_length:
|
|
927
|
+
continue
|
|
928
|
+
|
|
929
|
+
file_object.write(f"runEvent={tmp_items[0]},{tmp_items[1]}\n")
|
|
930
|
+
|
|
931
|
+
if include_guids:
|
|
932
|
+
run_event_guid_map[(tmp_items[0], tmp_items[1])] = [tmp_items[2]]
|
|
933
|
+
|
|
934
|
+
file_object.write(f"runEvtGuidMap={str(run_event_guid_map)}\n")
|
|
935
|
+
|
|
936
|
+
except Exception as e:
|
|
937
|
+
error_message = f"Cannot upload the Event Picking request: {str(e)}"
|
|
938
|
+
tmp_logger.error(error_message + traceback.format_exc())
|
|
939
|
+
return generate_response(False, f"ERROR: {error_message}")
|
|
940
|
+
|
|
941
|
+
tmp_logger.debug("Done")
|
|
942
|
+
return generate_response(True)
|