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,539 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
entry point
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import datetime
|
|
9
|
+
import decimal
|
|
10
|
+
import gzip
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import signal
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import traceback
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
from urllib.parse import parse_qsl
|
|
20
|
+
|
|
21
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
22
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
23
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
24
|
+
from pandacommon.pandautils.thread_utils import GenericThread
|
|
25
|
+
from werkzeug.datastructures import CombinedMultiDict, EnvironHeaders
|
|
26
|
+
from werkzeug.formparser import parse_form_data
|
|
27
|
+
|
|
28
|
+
import pandaserver.taskbuffer.ErrorCode
|
|
29
|
+
from pandaserver.api.v1 import credential_management_api as cred_api_v1
|
|
30
|
+
from pandaserver.api.v1 import data_carousel_api as data_carousel_api_v1
|
|
31
|
+
from pandaserver.api.v1 import event_api as event_api_v1
|
|
32
|
+
from pandaserver.api.v1 import file_server_api as file_server_api_v1
|
|
33
|
+
from pandaserver.api.v1 import harvester_api as harvester_api_v1
|
|
34
|
+
from pandaserver.api.v1 import idds_api as idds_api_v1
|
|
35
|
+
from pandaserver.api.v1 import job_api as job_api_v1
|
|
36
|
+
from pandaserver.api.v1 import metaconfig_api as metaconfig_api_v1
|
|
37
|
+
from pandaserver.api.v1 import pilot_api as pilot_api_v1
|
|
38
|
+
from pandaserver.api.v1 import statistics_api as statistics_api_v1
|
|
39
|
+
from pandaserver.api.v1 import system_api as system_api_v1
|
|
40
|
+
from pandaserver.api.v1 import task_api as task_api_v1
|
|
41
|
+
from pandaserver.api.v1 import workflow_api as workflow_api_v1
|
|
42
|
+
from pandaserver.api.v1.common import extract_allowed_methods
|
|
43
|
+
from pandaserver.config import panda_config
|
|
44
|
+
|
|
45
|
+
# pylint: disable=W0611
|
|
46
|
+
from pandaserver.jobdispatcher.JobDispatcher import (
|
|
47
|
+
ackCommands,
|
|
48
|
+
checkEventsAvailability,
|
|
49
|
+
checkJobStatus,
|
|
50
|
+
get_access_token,
|
|
51
|
+
get_events_status,
|
|
52
|
+
get_max_worker_id,
|
|
53
|
+
get_token_key,
|
|
54
|
+
getCommands,
|
|
55
|
+
getEventRanges,
|
|
56
|
+
getJob,
|
|
57
|
+
getKeyPair,
|
|
58
|
+
getProxy,
|
|
59
|
+
getResourceTypes,
|
|
60
|
+
getStatus,
|
|
61
|
+
jobDispatcher,
|
|
62
|
+
updateEventRange,
|
|
63
|
+
updateEventRanges,
|
|
64
|
+
updateJob,
|
|
65
|
+
updateJobsInBulk,
|
|
66
|
+
updateWorkerPilotStatus,
|
|
67
|
+
)
|
|
68
|
+
from pandaserver.srvcore import CoreUtils
|
|
69
|
+
|
|
70
|
+
# IMPORTANT: Add any new methods here to allow them to be called from the web I/F
|
|
71
|
+
from pandaserver.srvcore.allowed_methods import allowed_methods
|
|
72
|
+
from pandaserver.srvcore.panda_request import PandaRequest
|
|
73
|
+
from pandaserver.taskbuffer.Initializer import initializer
|
|
74
|
+
from pandaserver.taskbuffer.TaskBuffer import taskBuffer
|
|
75
|
+
|
|
76
|
+
# pylint: disable=W0611
|
|
77
|
+
from pandaserver.taskbuffer.Utils import (
|
|
78
|
+
delete_checkpoint,
|
|
79
|
+
deleteFile,
|
|
80
|
+
fetchLog,
|
|
81
|
+
getAttr,
|
|
82
|
+
getServer,
|
|
83
|
+
getVomsAttr,
|
|
84
|
+
isAlive,
|
|
85
|
+
put_checkpoint,
|
|
86
|
+
put_file_recovery_request,
|
|
87
|
+
put_workflow_request,
|
|
88
|
+
putEventPickingRequest,
|
|
89
|
+
putFile,
|
|
90
|
+
touchFile,
|
|
91
|
+
updateLog,
|
|
92
|
+
uploadLog,
|
|
93
|
+
)
|
|
94
|
+
from pandaserver.userinterface import Client
|
|
95
|
+
|
|
96
|
+
# pylint: disable=W0611
|
|
97
|
+
from pandaserver.userinterface.UserIF import (
|
|
98
|
+
addHarvesterDialogs,
|
|
99
|
+
avalancheTask,
|
|
100
|
+
changeTaskAttributePanda,
|
|
101
|
+
changeTaskModTimePanda,
|
|
102
|
+
changeTaskPriority,
|
|
103
|
+
changeTaskSplitRulePanda,
|
|
104
|
+
checkSandboxFile,
|
|
105
|
+
enableJumboJobs,
|
|
106
|
+
execute_idds_workflow_command,
|
|
107
|
+
finishTask,
|
|
108
|
+
get_ban_users,
|
|
109
|
+
get_files_in_datasets,
|
|
110
|
+
get_job_statistics_per_site_label_resource,
|
|
111
|
+
get_user_secrets,
|
|
112
|
+
getFullJobStatus,
|
|
113
|
+
getJediTaskDetails,
|
|
114
|
+
getJediTasksInTimeRange,
|
|
115
|
+
getJobStatisticsPerSite,
|
|
116
|
+
getJobStatus,
|
|
117
|
+
getJumboJobDatasets,
|
|
118
|
+
getPandaIDsWithTaskID,
|
|
119
|
+
getScriptOfflineRunning,
|
|
120
|
+
getTaskParamsMap,
|
|
121
|
+
getTaskStatus,
|
|
122
|
+
getUserJobMetadata,
|
|
123
|
+
getWorkerStats,
|
|
124
|
+
harvesterIsAlive,
|
|
125
|
+
increaseAttemptNrPanda,
|
|
126
|
+
insertSandboxFileInfo,
|
|
127
|
+
insertTaskParams,
|
|
128
|
+
killJobs,
|
|
129
|
+
killTask,
|
|
130
|
+
killUnfinishedJobs,
|
|
131
|
+
pauseTask,
|
|
132
|
+
reactivateTask,
|
|
133
|
+
reassignJobs,
|
|
134
|
+
reassignShare,
|
|
135
|
+
reassignTask,
|
|
136
|
+
relay_idds_command,
|
|
137
|
+
release_task,
|
|
138
|
+
reloadInput,
|
|
139
|
+
reportWorkerStats_jobtype,
|
|
140
|
+
resumeTask,
|
|
141
|
+
retryTask,
|
|
142
|
+
send_command_to_job,
|
|
143
|
+
set_user_secret,
|
|
144
|
+
setDebugMode,
|
|
145
|
+
setNumSlotsForWP,
|
|
146
|
+
submitJobs,
|
|
147
|
+
sweepPQ,
|
|
148
|
+
updateServiceMetrics,
|
|
149
|
+
updateWorkers,
|
|
150
|
+
userIF,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
_logger = PandaLogger().getLogger("Entry")
|
|
154
|
+
|
|
155
|
+
LATEST = "1"
|
|
156
|
+
|
|
157
|
+
# generate the allowed methods dynamically with all function names present in the API modules,
|
|
158
|
+
# excluding functions imported from other modules or the init_task_buffer function
|
|
159
|
+
cred_api_v1_methods = extract_allowed_methods(cred_api_v1)
|
|
160
|
+
data_carousel_api_v1_methods = extract_allowed_methods(data_carousel_api_v1)
|
|
161
|
+
event_api_v1_methods = extract_allowed_methods(event_api_v1)
|
|
162
|
+
file_server_api_v1_methods = extract_allowed_methods(file_server_api_v1)
|
|
163
|
+
harvester_api_v1_methods = extract_allowed_methods(harvester_api_v1)
|
|
164
|
+
idds_api_v1_methods = extract_allowed_methods(idds_api_v1)
|
|
165
|
+
job_api_v1_methods = extract_allowed_methods(job_api_v1)
|
|
166
|
+
metaconfig_api_v1_methods = extract_allowed_methods(metaconfig_api_v1)
|
|
167
|
+
pilot_api_v1_methods = extract_allowed_methods(pilot_api_v1)
|
|
168
|
+
statistics_api_v1_methods = extract_allowed_methods(statistics_api_v1)
|
|
169
|
+
system_api_v1_methods = extract_allowed_methods(system_api_v1)
|
|
170
|
+
task_api_v1_methods = extract_allowed_methods(task_api_v1)
|
|
171
|
+
workflow_api_v1_methods = extract_allowed_methods(workflow_api_v1)
|
|
172
|
+
|
|
173
|
+
# initialize oracledb using dummy connection
|
|
174
|
+
initializer.init()
|
|
175
|
+
|
|
176
|
+
# initialize TaskBuffer
|
|
177
|
+
requester_id = GenericThread().get_full_id(__name__, sys.modules[__name__].__file__)
|
|
178
|
+
taskBuffer.init(
|
|
179
|
+
panda_config.dbhost,
|
|
180
|
+
panda_config.dbpasswd,
|
|
181
|
+
nDBConnection=panda_config.nDBConnection,
|
|
182
|
+
useTimeout=True,
|
|
183
|
+
requester=requester_id,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if panda_config.nDBConnection != 0:
|
|
187
|
+
# initialize all the API modules
|
|
188
|
+
cred_api_v1.init_task_buffer(taskBuffer)
|
|
189
|
+
data_carousel_api_v1.init_task_buffer(taskBuffer)
|
|
190
|
+
event_api_v1.init_task_buffer(taskBuffer)
|
|
191
|
+
file_server_api_v1.init_task_buffer(taskBuffer)
|
|
192
|
+
harvester_api_v1.init_task_buffer(taskBuffer)
|
|
193
|
+
# IDDS API does not need to be initialized. idds_server_api_v1.init_task_buffer(taskBuffer)
|
|
194
|
+
job_api_v1.init_task_buffer(taskBuffer)
|
|
195
|
+
metaconfig_api_v1.init_task_buffer(taskBuffer)
|
|
196
|
+
pilot_api_v1.init_task_buffer(taskBuffer)
|
|
197
|
+
statistics_api_v1.init_task_buffer(taskBuffer)
|
|
198
|
+
# System API does not need to be initialized. system_api_v1.init_task_buffer(taskBuffer)
|
|
199
|
+
task_api_v1.init_task_buffer(taskBuffer)
|
|
200
|
+
workflow_api_v1.init_task_buffer(taskBuffer)
|
|
201
|
+
|
|
202
|
+
# initialize JobDispatcher
|
|
203
|
+
jobDispatcher.init(taskBuffer)
|
|
204
|
+
|
|
205
|
+
# initialize UserIF
|
|
206
|
+
userIF.init(taskBuffer)
|
|
207
|
+
|
|
208
|
+
# ban list
|
|
209
|
+
if panda_config.nDBConnection != 0:
|
|
210
|
+
# get ban list directly from the database
|
|
211
|
+
ban_user_list = CoreUtils.CachedObject("ban_list", 600, taskBuffer.get_ban_users, _logger)
|
|
212
|
+
else:
|
|
213
|
+
# get ban list from remote
|
|
214
|
+
ban_user_list = CoreUtils.CachedObject("ban_list", 600, Client.get_banned_users, _logger)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def pre_validate_request(panda_request):
|
|
218
|
+
# check authentication
|
|
219
|
+
if not panda_request.authenticated:
|
|
220
|
+
error_message = f"Token authentication failed. {panda_request.message}"
|
|
221
|
+
return error_message
|
|
222
|
+
|
|
223
|
+
# check list of banned users
|
|
224
|
+
username = panda_request.subprocess_env.get("SSL_CLIENT_S_DN", None)
|
|
225
|
+
if username:
|
|
226
|
+
username = CoreUtils.clean_user_id(username)
|
|
227
|
+
if username in ban_user_list:
|
|
228
|
+
error_message = f"{username} is banned"
|
|
229
|
+
return error_message
|
|
230
|
+
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def read_body(environ, content_length):
|
|
235
|
+
# read body contents
|
|
236
|
+
body = b""
|
|
237
|
+
while content_length > 0:
|
|
238
|
+
chunk = environ["wsgi.input"].read(min(content_length, 1024 * 1024))
|
|
239
|
+
if not chunk:
|
|
240
|
+
break
|
|
241
|
+
content_length -= len(chunk)
|
|
242
|
+
body += chunk
|
|
243
|
+
if content_length > 0:
|
|
244
|
+
# OSError is caught in the main function and forces killing the process
|
|
245
|
+
raise OSError(f"partial read from client. {content_length} bytes remaining")
|
|
246
|
+
|
|
247
|
+
return body
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def parse_qsl_parameters(environ, body, request_method):
|
|
251
|
+
# parse parameters for non-json requests
|
|
252
|
+
environ["wsgi.input"] = io.BytesIO(body)
|
|
253
|
+
environ["CONTENT_LENGTH"] = str(len(body))
|
|
254
|
+
environ["wsgi.headers"] = EnvironHeaders(environ)
|
|
255
|
+
|
|
256
|
+
# In the case of GET, HEAD methods we need to parse the query string list in the URL looking for parameters
|
|
257
|
+
if request_method in ["GET", "HEAD"]:
|
|
258
|
+
# Parse the query string list in the URL looking for parameters. Repeated query parameters submitted multiple times will be appended to a list
|
|
259
|
+
results_tmp = defaultdict(list)
|
|
260
|
+
parameter_list = parse_qsl(environ.get("QUERY_STRING", ""), keep_blank_values=True)
|
|
261
|
+
for key, value in parameter_list:
|
|
262
|
+
results_tmp[key].append(value)
|
|
263
|
+
|
|
264
|
+
params = {key: values[0] if len(values) == 1 else values for key, values in results_tmp.items()}
|
|
265
|
+
|
|
266
|
+
# In the case of POST, PUT methods we need to parse the form data
|
|
267
|
+
else:
|
|
268
|
+
# Parse form data. Combine the form (string fields) and the files (file uploads) into a single object
|
|
269
|
+
_, form, files = parse_form_data(environ)
|
|
270
|
+
|
|
271
|
+
# Combine the form and files into a single dictionary
|
|
272
|
+
params = dict(CombinedMultiDict([form, files]))
|
|
273
|
+
return params
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def parse_json_parameters_legacy(body):
|
|
277
|
+
# parse parameters for json requests
|
|
278
|
+
# decompress the body, this was done without checking the content encoding
|
|
279
|
+
body = gzip.decompress(body)
|
|
280
|
+
|
|
281
|
+
# de-serialize the body and patch for True/False
|
|
282
|
+
params = json.loads(body)
|
|
283
|
+
for key in list(params):
|
|
284
|
+
if params[key] is True:
|
|
285
|
+
params[key] = "True"
|
|
286
|
+
elif params[key] is False:
|
|
287
|
+
params[key] = "False"
|
|
288
|
+
return params
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def parse_json_parameters(body, content_encoding):
|
|
292
|
+
# parse parameters for json requests
|
|
293
|
+
# decompress the body if necessary
|
|
294
|
+
if content_encoding == "gzip":
|
|
295
|
+
body = gzip.decompress(body)
|
|
296
|
+
|
|
297
|
+
# de-serialize the body
|
|
298
|
+
params = json.loads(body)
|
|
299
|
+
|
|
300
|
+
return params
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def parse_parameters(api_module, json_app, json_body, content_encoding, environ, body, request_method):
|
|
304
|
+
# parse parameters with the new refactored API
|
|
305
|
+
if is_new_api(api_module):
|
|
306
|
+
# the request specifies json and it's a PUT/POST request with the data in the body
|
|
307
|
+
if json_body:
|
|
308
|
+
return parse_json_parameters(body, content_encoding)
|
|
309
|
+
else:
|
|
310
|
+
return parse_qsl_parameters(environ, body, request_method)
|
|
311
|
+
|
|
312
|
+
# parse parameters conserving the legacy API logic
|
|
313
|
+
else:
|
|
314
|
+
# parse parameters for json requests with the legacy API, even for GET requests
|
|
315
|
+
if json_app:
|
|
316
|
+
return parse_json_parameters_legacy(body)
|
|
317
|
+
# parse parameters for non-json requests
|
|
318
|
+
else:
|
|
319
|
+
return parse_qsl_parameters(environ, body, request_method)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def is_new_api(api_module):
|
|
323
|
+
return api_module != "panda"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def parse_script_name(environ):
|
|
327
|
+
method_name = ""
|
|
328
|
+
api_module = ""
|
|
329
|
+
version = "v0"
|
|
330
|
+
|
|
331
|
+
if "SCRIPT_NAME" in environ:
|
|
332
|
+
script_name = environ["SCRIPT_NAME"]
|
|
333
|
+
fields = script_name.split("/")
|
|
334
|
+
|
|
335
|
+
# Legacy API: /server/panda/<method>
|
|
336
|
+
if script_name.startswith("/server/panda/") and len(fields) == 4:
|
|
337
|
+
api_module = "panda"
|
|
338
|
+
method_name = fields[-1]
|
|
339
|
+
|
|
340
|
+
# Refactored API: /api/<version>/<module>/<method>
|
|
341
|
+
elif script_name.startswith("/api/") and len(fields) == 5:
|
|
342
|
+
method_name = fields[-1]
|
|
343
|
+
api_module = fields[-2]
|
|
344
|
+
version = fields[-3]
|
|
345
|
+
if version == "latest":
|
|
346
|
+
version = LATEST
|
|
347
|
+
|
|
348
|
+
else:
|
|
349
|
+
_logger.error(f"Could not parse script name: {script_name}")
|
|
350
|
+
|
|
351
|
+
return method_name, api_module, version
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def module_mapping(version, api_module):
|
|
355
|
+
mapping = {
|
|
356
|
+
"v0": {"panda": {"module": None, "allowed_methods": allowed_methods}}, # legacy API uses globals instead of a particular module
|
|
357
|
+
"v1": {
|
|
358
|
+
"creds": {"module": cred_api_v1, "allowed_methods": cred_api_v1_methods},
|
|
359
|
+
"data_carousel": {"module": data_carousel_api_v1, "allowed_methods": data_carousel_api_v1_methods},
|
|
360
|
+
"event": {"module": event_api_v1, "allowed_methods": event_api_v1_methods},
|
|
361
|
+
"file_server": {"module": file_server_api_v1, "allowed_methods": file_server_api_v1_methods},
|
|
362
|
+
"harvester": {"module": harvester_api_v1, "allowed_methods": harvester_api_v1_methods},
|
|
363
|
+
"idds": {"module": idds_api_v1, "allowed_methods": idds_api_v1_methods},
|
|
364
|
+
"job": {"module": job_api_v1, "allowed_methods": job_api_v1_methods},
|
|
365
|
+
"metaconfig": {"module": metaconfig_api_v1, "allowed_methods": metaconfig_api_v1_methods},
|
|
366
|
+
"pilot": {"module": pilot_api_v1, "allowed_methods": pilot_api_v1_methods},
|
|
367
|
+
"statistics": {"module": statistics_api_v1, "allowed_methods": statistics_api_v1_methods},
|
|
368
|
+
"system": {"module": system_api_v1, "allowed_methods": system_api_v1_methods},
|
|
369
|
+
"task": {"module": task_api_v1, "allowed_methods": task_api_v1_methods},
|
|
370
|
+
"workflow": {"module": workflow_api_v1, "allowed_methods": workflow_api_v1_methods},
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
try:
|
|
374
|
+
return mapping[version][api_module]
|
|
375
|
+
except KeyError:
|
|
376
|
+
_logger.error(f"Could not find module {api_module} in API version {version}")
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def validate_method(method_name, api_module, version):
|
|
381
|
+
# We are in the refactored API and the method is not in the specific allowed list
|
|
382
|
+
mapping = module_mapping(version, api_module)
|
|
383
|
+
if mapping and method_name in mapping["allowed_methods"]:
|
|
384
|
+
return True
|
|
385
|
+
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
# Encoder: convert datetime → ISO string with a marker
|
|
390
|
+
def encode_special_cases(obj):
|
|
391
|
+
if isinstance(obj, datetime.datetime):
|
|
392
|
+
return {"__datetime__": obj.isoformat()}
|
|
393
|
+
if isinstance(obj, decimal.Decimal):
|
|
394
|
+
if obj == obj.to_integral_value():
|
|
395
|
+
return int(obj)
|
|
396
|
+
else:
|
|
397
|
+
return float(obj)
|
|
398
|
+
raise TypeError(f"Type not serializable for {obj} ({type(obj)})")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# This is the starting point for all WSGI requests
|
|
402
|
+
def application(environ, start_response):
|
|
403
|
+
# Parse the script name to retrieve method, module and version
|
|
404
|
+
method_name, api_module, version = parse_script_name(environ)
|
|
405
|
+
|
|
406
|
+
tmp_log = LogWrapper(_logger, f"PID={os.getpid()} module={api_module} method={method_name} version={version}", seeMem=True)
|
|
407
|
+
cont_length = int(environ.get("CONTENT_LENGTH", 0))
|
|
408
|
+
request_method = environ.get("REQUEST_METHOD", None) # GET, POST, PUT, DELETE
|
|
409
|
+
|
|
410
|
+
# see if we are on the new or old APIs
|
|
411
|
+
new_api = is_new_api(api_module)
|
|
412
|
+
|
|
413
|
+
# json app means the content type is application/json,
|
|
414
|
+
# while json body requires additionally to be a PUT or POST request, where the body is json encoded
|
|
415
|
+
if new_api:
|
|
416
|
+
json_app = environ.get("CONTENT_TYPE", None) == "application/json" or environ.get("HTTP_ACCEPT", None) == "application/json"
|
|
417
|
+
else:
|
|
418
|
+
json_app = environ.get("CONTENT_TYPE", None) == "application/json"
|
|
419
|
+
json_body = environ.get("CONTENT_TYPE", None) == "application/json" and request_method in ["PUT", "POST"]
|
|
420
|
+
|
|
421
|
+
# Content encoding specifies whether the body is compressed through gzip or others.
|
|
422
|
+
# No encoding usually means the body is not compressed
|
|
423
|
+
content_encoding = environ.get("HTTP_CONTENT_ENCODING")
|
|
424
|
+
|
|
425
|
+
tmp_log.debug(f"""start content-length={cont_length} json={json_app} origin={environ.get("HTTP_ORIGIN", None)}""")
|
|
426
|
+
|
|
427
|
+
start_time = naive_utcnow()
|
|
428
|
+
return_type = None
|
|
429
|
+
|
|
430
|
+
# check method name is allowed, otherwise return 403
|
|
431
|
+
if not validate_method(method_name, api_module, version):
|
|
432
|
+
error_message = f"method {method_name} is forbidden"
|
|
433
|
+
tmp_log.error(error_message)
|
|
434
|
+
start_response("403 Forbidden", [("Content-Type", "text/plain")])
|
|
435
|
+
return [f"ERROR : {error_message}".encode()]
|
|
436
|
+
|
|
437
|
+
# get the method object to be executed
|
|
438
|
+
try:
|
|
439
|
+
if new_api:
|
|
440
|
+
module = module_mapping(version, api_module)["module"]
|
|
441
|
+
tmp_method = getattr(module, method_name)
|
|
442
|
+
else:
|
|
443
|
+
tmp_method = globals()[method_name]
|
|
444
|
+
except Exception:
|
|
445
|
+
error_message = f"method {method_name} is undefined in {api_module} {version}"
|
|
446
|
+
tmp_log.error(error_message)
|
|
447
|
+
start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")])
|
|
448
|
+
return ["ERROR : {error_message}".encode()]
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
# generate a request object with the environment and the logger
|
|
452
|
+
panda_request = PandaRequest(environ, tmp_log)
|
|
453
|
+
|
|
454
|
+
# pre-validate the request
|
|
455
|
+
error_message = pre_validate_request(panda_request)
|
|
456
|
+
if error_message:
|
|
457
|
+
tmp_log.error(error_message)
|
|
458
|
+
start_response("403 Forbidden", [("Content-Type", "text/plain")])
|
|
459
|
+
return [f"ERROR : {error_message}".encode()]
|
|
460
|
+
|
|
461
|
+
# read the body of the request
|
|
462
|
+
body = read_body(environ, cont_length)
|
|
463
|
+
|
|
464
|
+
# parse the parameters
|
|
465
|
+
params = parse_parameters(api_module, json_app, json_body, content_encoding, environ, body, request_method)
|
|
466
|
+
|
|
467
|
+
if panda_config.entryVerbose:
|
|
468
|
+
tmp_log.debug(f"with {str(list(params))}")
|
|
469
|
+
|
|
470
|
+
# execute the method, passing along the request and the decoded parameters
|
|
471
|
+
param_list = [panda_request]
|
|
472
|
+
exec_result = tmp_method(*param_list, **params)
|
|
473
|
+
|
|
474
|
+
# extract return type
|
|
475
|
+
if isinstance(exec_result, dict) and "type" in exec_result and "content" in exec_result:
|
|
476
|
+
return_type = exec_result["type"]
|
|
477
|
+
exec_result = exec_result["content"]
|
|
478
|
+
|
|
479
|
+
# convert bool to string
|
|
480
|
+
if exec_result in [True, False]:
|
|
481
|
+
exec_result = str(exec_result)
|
|
482
|
+
|
|
483
|
+
# convert the response to JSON or str depending on HTTP_ACCEPT and CONTENT_TYPE
|
|
484
|
+
if new_api:
|
|
485
|
+
if json_app:
|
|
486
|
+
exec_result = json.dumps(exec_result, default=encode_special_cases)
|
|
487
|
+
elif not isinstance(exec_result, str):
|
|
488
|
+
exec_result = str(exec_result)
|
|
489
|
+
|
|
490
|
+
except Exception as exc:
|
|
491
|
+
tmp_log.error(f"execution failure : {str(exc)}\n {traceback.format_exc()}")
|
|
492
|
+
if hasattr(panda_config, "dumpBadRequest") and panda_config.dumpBadRequest:
|
|
493
|
+
try:
|
|
494
|
+
with tempfile.NamedTemporaryFile(delete=False, prefix="req_dump_") as file_object:
|
|
495
|
+
environ["WSGI_INPUT_DUMP"] = file_object.name
|
|
496
|
+
file_object.write(body)
|
|
497
|
+
os.chmod(file_object.name, 0o775)
|
|
498
|
+
except Exception:
|
|
499
|
+
tmp_log.error(traceback.format_exc())
|
|
500
|
+
pass
|
|
501
|
+
error_string = "\n".join(f"{tmp_key} : {str(tmp_value)}" for tmp_key, tmp_value in environ.items())
|
|
502
|
+
tmp_log.error(error_string)
|
|
503
|
+
|
|
504
|
+
# return internal server error
|
|
505
|
+
start_response("500 INTERNAL SERVER ERROR", [("Content-Type", "text/plain")])
|
|
506
|
+
# force kill to release memory
|
|
507
|
+
if isinstance(exc, OSError):
|
|
508
|
+
tmp_log.warning("force restart due")
|
|
509
|
+
os.kill(os.getpid(), signal.SIGINT)
|
|
510
|
+
|
|
511
|
+
return [str(exc).encode()]
|
|
512
|
+
|
|
513
|
+
if panda_config.entryVerbose:
|
|
514
|
+
tmp_log.debug("done")
|
|
515
|
+
|
|
516
|
+
# log execution time and return length
|
|
517
|
+
duration = naive_utcnow() - start_time
|
|
518
|
+
tmp_log.info(
|
|
519
|
+
f"exec_time={duration.seconds}.{duration.microseconds // 1000:03d} sec, return_type={return_type} real_type={type(exec_result).__name__} len={len(str(exec_result))} B"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# start the response and return result
|
|
523
|
+
if exec_result == pandaserver.taskbuffer.ErrorCode.EC_NotFound:
|
|
524
|
+
start_response("404 Not Found", [("Content-Type", "text/plain")])
|
|
525
|
+
return ["not found".encode()]
|
|
526
|
+
|
|
527
|
+
if exec_result == pandaserver.taskbuffer.ErrorCode.EC_Forbidden:
|
|
528
|
+
start_response("403 Forbidden", [("Content-Type", "text/plain")])
|
|
529
|
+
return ["forbidden".encode()]
|
|
530
|
+
|
|
531
|
+
if return_type == "json":
|
|
532
|
+
start_response("200 OK", [("Content-Type", "application/json")])
|
|
533
|
+
else:
|
|
534
|
+
start_response("200 OK", [("Content-Type", "text/plain")])
|
|
535
|
+
|
|
536
|
+
if isinstance(exec_result, str):
|
|
537
|
+
exec_result = exec_result.encode()
|
|
538
|
+
|
|
539
|
+
return [exec_result]
|