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,832 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
|
|
7
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
8
|
+
from pandacommon.pandautils.PandaUtils import get_sql_IN_bind_variables, naive_utcnow
|
|
9
|
+
|
|
10
|
+
from pandaserver.config import panda_config
|
|
11
|
+
from pandaserver.srvcore import CoreUtils
|
|
12
|
+
from pandaserver.taskbuffer import ErrorCode, JobUtils
|
|
13
|
+
from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule, varNUMBER
|
|
14
|
+
from pandaserver.taskbuffer.db_proxy_mods.entity_module import get_entity_module
|
|
15
|
+
from pandaserver.taskbuffer.JobSpec import JobSpec
|
|
16
|
+
from pandaserver.workflow.workflow_base import (
|
|
17
|
+
WFDataSpec,
|
|
18
|
+
WFDataStatus,
|
|
19
|
+
WFStepSpec,
|
|
20
|
+
WFStepStatus,
|
|
21
|
+
WorkflowSpec,
|
|
22
|
+
WorkflowStatus,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Module class to define methods related to workflow
|
|
27
|
+
class WorkflowModule(BaseModule):
|
|
28
|
+
# constructor
|
|
29
|
+
def __init__(self, log_stream: LogWrapper):
|
|
30
|
+
super().__init__(log_stream)
|
|
31
|
+
|
|
32
|
+
def get_workflow(self, workflow_id: int) -> WorkflowSpec | None:
|
|
33
|
+
"""
|
|
34
|
+
Retrieve a workflow specification by its ID
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
workflow_id (int): ID of the workflow to retrieve
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
WorkflowSpec | None: The workflow specification if found, otherwise None
|
|
41
|
+
"""
|
|
42
|
+
comment = " /* DBProxy.get_workflow */"
|
|
43
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
|
|
44
|
+
sql = f"SELECT {WorkflowSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflows " f"WHERE workflow_id=:workflow_id "
|
|
45
|
+
var_map = {":workflow_id": workflow_id}
|
|
46
|
+
self.cur.execute(sql + comment, var_map)
|
|
47
|
+
res_list = self.cur.fetchall()
|
|
48
|
+
if res_list is not None:
|
|
49
|
+
if len(res_list) > 1:
|
|
50
|
+
tmp_log.error("more than one workflows; unexpected")
|
|
51
|
+
else:
|
|
52
|
+
for res in res_list:
|
|
53
|
+
workflow_spec = WorkflowSpec()
|
|
54
|
+
workflow_spec.pack(res)
|
|
55
|
+
return workflow_spec
|
|
56
|
+
else:
|
|
57
|
+
tmp_log.warning("no workflow found; skipped")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def get_workflow_step(self, step_id: int) -> WFStepSpec | None:
|
|
61
|
+
"""
|
|
62
|
+
Retrieve a workflow step specification by its ID
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
step_id (int): ID of the workflow step to retrieve
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
WFStepSpec | None: The workflow step specification if found, otherwise None
|
|
69
|
+
"""
|
|
70
|
+
comment = " /* DBProxy.get_workflow_step */"
|
|
71
|
+
tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}")
|
|
72
|
+
sql = f"SELECT {WFStepSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_steps " f"WHERE step_id=:step_id "
|
|
73
|
+
var_map = {":step_id": step_id}
|
|
74
|
+
self.cur.execute(sql + comment, var_map)
|
|
75
|
+
res_list = self.cur.fetchall()
|
|
76
|
+
if res_list is not None:
|
|
77
|
+
if len(res_list) > 1:
|
|
78
|
+
tmp_log.error("more than one steps; unexpected")
|
|
79
|
+
else:
|
|
80
|
+
for res in res_list:
|
|
81
|
+
step_spec = WFStepSpec()
|
|
82
|
+
step_spec.pack(res)
|
|
83
|
+
return step_spec
|
|
84
|
+
else:
|
|
85
|
+
tmp_log.warning("no step found; skipped")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def get_workflow_data(self, data_id: int) -> WFDataSpec | None:
|
|
89
|
+
"""
|
|
90
|
+
Retrieve a workflow data specification by its ID
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
data_id (int): ID of the workflow data to retrieve
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
WFDataSpec | None: The workflow data specification if found, otherwise None
|
|
97
|
+
"""
|
|
98
|
+
comment = " /* DBProxy.get_workflow_data */"
|
|
99
|
+
tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}")
|
|
100
|
+
sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE data_id=:data_id "
|
|
101
|
+
var_map = {":data_id": data_id}
|
|
102
|
+
self.cur.execute(sql + comment, var_map)
|
|
103
|
+
res_list = self.cur.fetchall()
|
|
104
|
+
if res_list is not None:
|
|
105
|
+
if len(res_list) > 1:
|
|
106
|
+
tmp_log.error("more than one data; unexpected")
|
|
107
|
+
else:
|
|
108
|
+
for res in res_list:
|
|
109
|
+
data_spec = WFDataSpec()
|
|
110
|
+
data_spec.pack(res)
|
|
111
|
+
return data_spec
|
|
112
|
+
else:
|
|
113
|
+
tmp_log.warning("no data found; skipped")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def get_workflow_data_by_name(self, name: str, workflow_id: int | None) -> WFDataSpec | None:
|
|
117
|
+
"""
|
|
118
|
+
Retrieve a workflow data specification by its name and workflow ID
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name (str): Name of the workflow data to retrieve
|
|
122
|
+
workflow_id (int | None): ID of the workflow to which the data belongs (optional)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
WFDataSpec | None: The workflow data specification if found, otherwise None
|
|
126
|
+
"""
|
|
127
|
+
comment = " /* DBProxy.get_workflow_data_by_name */"
|
|
128
|
+
tmp_log = self.create_tagged_logger(comment, f"name={name}, workflow_id={workflow_id}")
|
|
129
|
+
sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE name=:name "
|
|
130
|
+
var_map = {":name": name}
|
|
131
|
+
if workflow_id is not None:
|
|
132
|
+
sql += "AND workflow_id=:workflow_id "
|
|
133
|
+
var_map[":workflow_id"] = workflow_id
|
|
134
|
+
self.cur.execute(sql + comment, var_map)
|
|
135
|
+
res_list = self.cur.fetchall()
|
|
136
|
+
if res_list is not None:
|
|
137
|
+
if len(res_list) > 1:
|
|
138
|
+
tmp_log.error("more than one data; unexpected")
|
|
139
|
+
return None
|
|
140
|
+
else:
|
|
141
|
+
for res in res_list:
|
|
142
|
+
data_spec = WFDataSpec()
|
|
143
|
+
data_spec.pack(res)
|
|
144
|
+
return data_spec
|
|
145
|
+
else:
|
|
146
|
+
tmp_log.warning("no data found; skipped")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def get_steps_of_workflow(self, workflow_id: int, status_filter_list: list | None = None, status_exclusion_list: list | None = None) -> list[WFStepSpec]:
|
|
150
|
+
"""
|
|
151
|
+
Retrieve all workflow steps for a given workflow ID
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
workflow_id (int): ID of the workflow to retrieve steps for
|
|
155
|
+
status_filter_list (list | None): List of statuses to filter the steps by (optional)
|
|
156
|
+
status_exclusion_list (list | None): List of statuses to exclude the steps by (optional)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
list[WFStepSpec]: List of workflow step specifications
|
|
160
|
+
"""
|
|
161
|
+
comment = " /* DBProxy.get_steps_of_workflow */"
|
|
162
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
|
|
163
|
+
sql = f"SELECT {WFStepSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_steps " f"WHERE workflow_id=:workflow_id "
|
|
164
|
+
var_map = {":workflow_id": workflow_id}
|
|
165
|
+
if status_filter_list:
|
|
166
|
+
status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
|
|
167
|
+
sql += f"AND status IN ({status_var_names_str}) "
|
|
168
|
+
var_map.update(status_var_map)
|
|
169
|
+
if status_exclusion_list:
|
|
170
|
+
antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
|
|
171
|
+
sql += f"AND status NOT IN ({antistatus_var_names_str}) "
|
|
172
|
+
var_map.update(antistatus_var_map)
|
|
173
|
+
sql += "ORDER BY step_id "
|
|
174
|
+
self.cur.execute(sql + comment, var_map)
|
|
175
|
+
res_list = self.cur.fetchall()
|
|
176
|
+
if res_list is not None:
|
|
177
|
+
step_specs = []
|
|
178
|
+
for res in res_list:
|
|
179
|
+
step_spec = WFStepSpec()
|
|
180
|
+
step_spec.pack(res)
|
|
181
|
+
step_specs.append(step_spec)
|
|
182
|
+
return step_specs
|
|
183
|
+
else:
|
|
184
|
+
tmp_log.warning("no steps found; skipped")
|
|
185
|
+
return []
|
|
186
|
+
|
|
187
|
+
def get_data_of_workflow(
|
|
188
|
+
self, workflow_id: int, status_filter_list: list | None = None, status_exclusion_list: list | None = None, type_filter_list: list | None = None
|
|
189
|
+
) -> list[WFDataSpec]:
|
|
190
|
+
"""
|
|
191
|
+
Retrieve all workflow data for a given workflow ID
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
workflow_id (int): ID of the workflow to retrieve data for
|
|
195
|
+
status_filter_list (list | None): List of statuses to filter the data by (optional)
|
|
196
|
+
status_exclusion_list (list | None): List of statuses to exclude the data by (optional)
|
|
197
|
+
type_filter_list (list | None): List of types to filter the data by (optional)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
list[WFDataSpec]: List of workflow data specifications
|
|
201
|
+
"""
|
|
202
|
+
comment = " /* DBProxy.get_data_of_workflow */"
|
|
203
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
|
|
204
|
+
sql = f"SELECT {WFDataSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflow_data " f"WHERE workflow_id=:workflow_id "
|
|
205
|
+
var_map = {":workflow_id": workflow_id}
|
|
206
|
+
if status_filter_list:
|
|
207
|
+
status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
|
|
208
|
+
sql += f"AND status IN ({status_var_names_str}) "
|
|
209
|
+
var_map.update(status_var_map)
|
|
210
|
+
if status_exclusion_list:
|
|
211
|
+
antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
|
|
212
|
+
sql += f"AND status NOT IN ({antistatus_var_names_str}) "
|
|
213
|
+
var_map.update(antistatus_var_map)
|
|
214
|
+
if type_filter_list:
|
|
215
|
+
type_var_names_str, type_var_map = get_sql_IN_bind_variables(type_filter_list, prefix=":type")
|
|
216
|
+
sql += f"AND type IN ({type_var_names_str}) "
|
|
217
|
+
var_map.update(type_var_map)
|
|
218
|
+
sql += "ORDER BY data_id "
|
|
219
|
+
self.cur.execute(sql + comment, var_map)
|
|
220
|
+
res_list = self.cur.fetchall()
|
|
221
|
+
if res_list is not None:
|
|
222
|
+
data_specs = []
|
|
223
|
+
for res in res_list:
|
|
224
|
+
data_spec = WFDataSpec()
|
|
225
|
+
data_spec.pack(res)
|
|
226
|
+
data_specs.append(data_spec)
|
|
227
|
+
return data_specs
|
|
228
|
+
else:
|
|
229
|
+
tmp_log.warning("no data found; skipped")
|
|
230
|
+
return []
|
|
231
|
+
|
|
232
|
+
def query_workflows(
|
|
233
|
+
self, status_filter_list: list | None = None, status_exclusion_list: list | None = None, check_interval_sec: int = 300
|
|
234
|
+
) -> list[WorkflowSpec]:
|
|
235
|
+
"""
|
|
236
|
+
Retrieve list of workflows with optional status filtering
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
status_filter_list (list | None): List of statuses to filter the workflows by (optional)
|
|
240
|
+
status_exclusion_list (list | None): List of statuses to exclude the workflows by (optional)
|
|
241
|
+
check_interval_sec (int): Time in seconds to wait between checks (default: 300)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
list[WorkflowSpec]: List of workflow specifications
|
|
245
|
+
"""
|
|
246
|
+
comment = " /* DBProxy.query_workflows */"
|
|
247
|
+
tmp_log = self.create_tagged_logger(comment, "query_workflows")
|
|
248
|
+
tmp_log.debug(f"start, status_filter_list={status_filter_list} status_exclusion_list={status_exclusion_list} check_interval_sec={check_interval_sec}")
|
|
249
|
+
sql = f"SELECT {WorkflowSpec.columnNames()} " f"FROM {panda_config.schemaJEDI}.workflows " f"WHERE (check_time IS NULL OR check_time<:check_time) "
|
|
250
|
+
now_time = naive_utcnow()
|
|
251
|
+
var_map = {":check_time": now_time - timedelta(seconds=check_interval_sec)}
|
|
252
|
+
if status_filter_list:
|
|
253
|
+
status_var_names_str, status_var_map = get_sql_IN_bind_variables(status_filter_list, prefix=":status")
|
|
254
|
+
sql += f"AND status IN ({status_var_names_str}) "
|
|
255
|
+
var_map.update(status_var_map)
|
|
256
|
+
if status_exclusion_list:
|
|
257
|
+
antistatus_var_names_str, antistatus_var_map = get_sql_IN_bind_variables(status_exclusion_list, prefix=":antistatus")
|
|
258
|
+
sql += f"AND status NOT IN ({antistatus_var_names_str}) "
|
|
259
|
+
var_map.update(antistatus_var_map)
|
|
260
|
+
sql += "ORDER BY check_time, creation_time "
|
|
261
|
+
self.cur.execute(sql + comment, var_map)
|
|
262
|
+
res_list = self.cur.fetchall()
|
|
263
|
+
if res_list is not None:
|
|
264
|
+
workflow_specs = []
|
|
265
|
+
for res in res_list:
|
|
266
|
+
workflow_spec = WorkflowSpec()
|
|
267
|
+
workflow_spec.pack(res)
|
|
268
|
+
workflow_specs.append(workflow_spec)
|
|
269
|
+
tmp_log.debug(f"got {len(workflow_specs)} workflows")
|
|
270
|
+
return workflow_specs
|
|
271
|
+
else:
|
|
272
|
+
tmp_log.warning("no workflows found; skipped")
|
|
273
|
+
return []
|
|
274
|
+
|
|
275
|
+
def lock_workflow(self, workflow_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
|
|
276
|
+
"""
|
|
277
|
+
Lock a workflow to prevent concurrent modifications
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
workflow_id (int): ID of the workflow to lock
|
|
281
|
+
locked_by (str): Identifier of the entity locking the workflow
|
|
282
|
+
lock_expiration_sec (int): Time in seconds after which the lock expires
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
bool | None: True if the lock was acquired, False if not, None if an error occurred
|
|
286
|
+
"""
|
|
287
|
+
comment = " /* DBProxy.lock_workflow */"
|
|
288
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}, locked_by={locked_by}")
|
|
289
|
+
tmp_log.debug("start")
|
|
290
|
+
try:
|
|
291
|
+
now_time = naive_utcnow()
|
|
292
|
+
sql_lock = (
|
|
293
|
+
f"UPDATE {panda_config.schemaJEDI}.workflows "
|
|
294
|
+
"SET locked_by=:locked_by, lock_time=:lock_time "
|
|
295
|
+
"WHERE workflow_id=:workflow_id "
|
|
296
|
+
"AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
|
|
297
|
+
)
|
|
298
|
+
var_map = {
|
|
299
|
+
":locked_by": locked_by,
|
|
300
|
+
":lock_time": now_time,
|
|
301
|
+
":workflow_id": workflow_id,
|
|
302
|
+
":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
|
|
303
|
+
}
|
|
304
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
305
|
+
cur.execute(sql_lock + comment, var_map)
|
|
306
|
+
row_count = cur.rowcount
|
|
307
|
+
if row_count is None:
|
|
308
|
+
tmp_log.error(f"failed to update DB to lock; skipped")
|
|
309
|
+
elif row_count > 1:
|
|
310
|
+
tmp_log.error(f"more than one workflow updated to lock; unexpected")
|
|
311
|
+
elif row_count == 0:
|
|
312
|
+
# no row updated; did not get the lock
|
|
313
|
+
tmp_log.debug(f"did not get lock; skipped")
|
|
314
|
+
return False
|
|
315
|
+
elif row_count == 1:
|
|
316
|
+
# successfully locked the workflow
|
|
317
|
+
tmp_log.debug(f"got lock")
|
|
318
|
+
return True
|
|
319
|
+
except Exception as e:
|
|
320
|
+
tmp_log.error(f"failed to lock workflow: {e}")
|
|
321
|
+
|
|
322
|
+
def unlock_workflow(self, workflow_id: int, locked_by: str) -> bool | None:
|
|
323
|
+
"""
|
|
324
|
+
Unlock a workflow to allow modifications
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
workflow_id (int): ID of the workflow to unlock
|
|
328
|
+
locked_by (str): Identifier of the entity unlocking the workflow
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
bool | None: True if the unlock was successful, False if not, None if an error occurred
|
|
332
|
+
"""
|
|
333
|
+
comment = " /* DBProxy.unlock_workflow */"
|
|
334
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}, locked_by={locked_by}")
|
|
335
|
+
tmp_log.debug("start")
|
|
336
|
+
try:
|
|
337
|
+
sql_unlock = (
|
|
338
|
+
f"UPDATE {panda_config.schemaJEDI}.workflows " "SET locked_by=NULL, lock_time=NULL " "WHERE workflow_id=:workflow_id AND locked_by=:locked_by"
|
|
339
|
+
)
|
|
340
|
+
var_map = {":workflow_id": workflow_id, ":locked_by": locked_by}
|
|
341
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
342
|
+
cur.execute(sql_unlock + comment, var_map)
|
|
343
|
+
row_count = cur.rowcount
|
|
344
|
+
if row_count is None:
|
|
345
|
+
tmp_log.error(f"failed to update DB to unlock; skipped")
|
|
346
|
+
elif row_count > 1:
|
|
347
|
+
tmp_log.error(f"more than one workflow updated to unlock; unexpected")
|
|
348
|
+
elif row_count == 0:
|
|
349
|
+
# no row updated; did not get the unlock
|
|
350
|
+
tmp_log.debug(f"no workflow updated to unlock; skipped")
|
|
351
|
+
return False
|
|
352
|
+
elif row_count == 1:
|
|
353
|
+
# successfully unlocked the workflow
|
|
354
|
+
tmp_log.debug(f"released lock")
|
|
355
|
+
return True
|
|
356
|
+
except Exception as e:
|
|
357
|
+
tmp_log.error(f"failed to unlock workflow: {e}")
|
|
358
|
+
|
|
359
|
+
def lock_workflow_step(self, step_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
|
|
360
|
+
"""
|
|
361
|
+
Lock a workflow step to prevent concurrent modifications
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
step_id (int): ID of the workflow step to lock
|
|
365
|
+
locked_by (str): Identifier of the entity locking the workflow step
|
|
366
|
+
lock_expiration_sec (int): Time in seconds after which the lock expires
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
bool | None: True if the lock was acquired, False if not, None if an error occurred
|
|
370
|
+
"""
|
|
371
|
+
comment = " /* DBProxy.lock_workflow_step */"
|
|
372
|
+
tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}, locked_by={locked_by}")
|
|
373
|
+
tmp_log.debug("start")
|
|
374
|
+
try:
|
|
375
|
+
now_time = naive_utcnow()
|
|
376
|
+
sql_lock = (
|
|
377
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_steps "
|
|
378
|
+
"SET locked_by=:locked_by, lock_time=:lock_time "
|
|
379
|
+
"WHERE step_id=:step_id "
|
|
380
|
+
"AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
|
|
381
|
+
)
|
|
382
|
+
var_map = {
|
|
383
|
+
":locked_by": locked_by,
|
|
384
|
+
":lock_time": now_time,
|
|
385
|
+
":step_id": step_id,
|
|
386
|
+
":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
|
|
387
|
+
}
|
|
388
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
389
|
+
cur.execute(sql_lock + comment, var_map)
|
|
390
|
+
row_count = cur.rowcount
|
|
391
|
+
if row_count is None:
|
|
392
|
+
tmp_log.error(f"failed to update DB to lock; skipped")
|
|
393
|
+
elif row_count > 1:
|
|
394
|
+
tmp_log.error(f"more than one step updated to lock; unexpected")
|
|
395
|
+
elif row_count == 0:
|
|
396
|
+
# no row updated; did not get the lock
|
|
397
|
+
tmp_log.debug(f"did not get lock; skipped")
|
|
398
|
+
return False
|
|
399
|
+
elif row_count == 1:
|
|
400
|
+
# successfully locked the workflow step
|
|
401
|
+
tmp_log.debug(f"got lock")
|
|
402
|
+
return True
|
|
403
|
+
except Exception as e:
|
|
404
|
+
tmp_log.error(f"failed to lock workflow step: {e}")
|
|
405
|
+
|
|
406
|
+
def unlock_workflow_step(self, step_id: int, locked_by: str) -> bool | None:
|
|
407
|
+
"""
|
|
408
|
+
Unlock a workflow step to allow modifications
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
step_id (int): ID of the workflow step to unlock
|
|
412
|
+
locked_by (str): Identifier of the entity unlocking the workflow step
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
bool | None: True if the unlock was successful, False if not, None if an error occurred
|
|
416
|
+
"""
|
|
417
|
+
comment = " /* DBProxy.unlock_workflow_step */"
|
|
418
|
+
tmp_log = self.create_tagged_logger(comment, f"step_id={step_id}, locked_by={locked_by}")
|
|
419
|
+
tmp_log.debug("start")
|
|
420
|
+
try:
|
|
421
|
+
sql_unlock = (
|
|
422
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_steps " "SET locked_by=NULL, lock_time=NULL " "WHERE step_id=:step_id AND locked_by=:locked_by"
|
|
423
|
+
)
|
|
424
|
+
var_map = {":step_id": step_id, ":locked_by": locked_by}
|
|
425
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
426
|
+
cur.execute(sql_unlock + comment, var_map)
|
|
427
|
+
row_count = cur.rowcount
|
|
428
|
+
if row_count is None:
|
|
429
|
+
tmp_log.error(f"failed to update DB to unlock; skipped")
|
|
430
|
+
elif row_count > 1:
|
|
431
|
+
tmp_log.error(f"more than one step updated to unlock; unexpected")
|
|
432
|
+
elif row_count == 0:
|
|
433
|
+
# no row updated; did not get the unlock
|
|
434
|
+
tmp_log.debug(f"no step updated to unlock; skipped")
|
|
435
|
+
return False
|
|
436
|
+
elif row_count == 1:
|
|
437
|
+
# successfully unlocked the workflow step
|
|
438
|
+
tmp_log.debug(f"released lock")
|
|
439
|
+
return True
|
|
440
|
+
except Exception as e:
|
|
441
|
+
tmp_log.error(f"failed to unlock workflow step: {e}")
|
|
442
|
+
|
|
443
|
+
def lock_workflow_data(self, data_id: int, locked_by: str, lock_expiration_sec: int = 120) -> bool | None:
|
|
444
|
+
"""
|
|
445
|
+
Lock a workflow data to prevent concurrent modifications
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
data_id (int): ID of the workflow data to lock
|
|
449
|
+
locked_by (str): Identifier of the entity locking the workflow data
|
|
450
|
+
lock_expiration_sec (int): Time in seconds after which the lock expires
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
bool | None: True if the lock was acquired, False if not, None if an error occurred
|
|
454
|
+
"""
|
|
455
|
+
comment = " /* DBProxy.lock_workflow_data */"
|
|
456
|
+
tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}, locked_by={locked_by}")
|
|
457
|
+
tmp_log.debug("start")
|
|
458
|
+
try:
|
|
459
|
+
now_time = naive_utcnow()
|
|
460
|
+
sql_lock = (
|
|
461
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_data "
|
|
462
|
+
"SET locked_by=:locked_by, lock_time=:lock_time "
|
|
463
|
+
"WHERE data_id=:data_id "
|
|
464
|
+
"AND (locked_by IS NULL OR locked_by=:locked_by OR lock_time<:min_lock_time)"
|
|
465
|
+
)
|
|
466
|
+
var_map = {
|
|
467
|
+
":locked_by": locked_by,
|
|
468
|
+
":lock_time": now_time,
|
|
469
|
+
":data_id": data_id,
|
|
470
|
+
":min_lock_time": now_time - timedelta(seconds=lock_expiration_sec),
|
|
471
|
+
}
|
|
472
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
473
|
+
cur.execute(sql_lock + comment, var_map)
|
|
474
|
+
row_count = cur.rowcount
|
|
475
|
+
if row_count is None:
|
|
476
|
+
tmp_log.error(f"failed to update DB to lock; skipped")
|
|
477
|
+
elif row_count > 1:
|
|
478
|
+
tmp_log.error(f"more than one data updated to lock; unexpected")
|
|
479
|
+
elif row_count == 0:
|
|
480
|
+
# no row updated; did not get the lock
|
|
481
|
+
tmp_log.debug(f"did not get lock; skipped")
|
|
482
|
+
return False
|
|
483
|
+
elif row_count == 1:
|
|
484
|
+
# successfully locked the workflow data
|
|
485
|
+
tmp_log.debug(f"got lock")
|
|
486
|
+
return True
|
|
487
|
+
except Exception as e:
|
|
488
|
+
tmp_log.error(f"failed to lock workflow data: {e}")
|
|
489
|
+
|
|
490
|
+
def unlock_workflow_data(self, data_id: int, locked_by: str) -> bool | None:
|
|
491
|
+
"""
|
|
492
|
+
Unlock a workflow data to allow modifications
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
data_id (int): ID of the workflow data to unlock
|
|
496
|
+
locked_by (str): Identifier of the entity unlocking the workflow data
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
bool | None: True if the unlock was successful, False if not, None if an error occurred
|
|
500
|
+
"""
|
|
501
|
+
comment = " /* DBProxy.unlock_workflow_data */"
|
|
502
|
+
tmp_log = self.create_tagged_logger(comment, f"data_id={data_id}, locked_by={locked_by}")
|
|
503
|
+
tmp_log.debug("start")
|
|
504
|
+
try:
|
|
505
|
+
sql_unlock = (
|
|
506
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_data " "SET locked_by=NULL, lock_time=NULL " "WHERE data_id=:data_id AND locked_by=:locked_by"
|
|
507
|
+
)
|
|
508
|
+
var_map = {":data_id": data_id, ":locked_by": locked_by}
|
|
509
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
510
|
+
cur.execute(sql_unlock + comment, var_map)
|
|
511
|
+
row_count = cur.rowcount
|
|
512
|
+
if row_count is None:
|
|
513
|
+
tmp_log.error(f"failed to update DB to unlock; skipped")
|
|
514
|
+
elif row_count > 1:
|
|
515
|
+
tmp_log.error(f"more than one data updated to unlock; unexpected")
|
|
516
|
+
elif row_count == 0:
|
|
517
|
+
# no row updated; did not get the unlock
|
|
518
|
+
tmp_log.debug(f"no data updated to unlock; skipped")
|
|
519
|
+
return False
|
|
520
|
+
elif row_count == 1:
|
|
521
|
+
# successfully unlocked the workflow data
|
|
522
|
+
tmp_log.debug(f"released lock")
|
|
523
|
+
return True
|
|
524
|
+
except Exception as e:
|
|
525
|
+
tmp_log.error(f"failed to unlock workflow data: {e}")
|
|
526
|
+
|
|
527
|
+
def insert_workflow(self, workflow_spec: WorkflowSpec) -> int | None:
|
|
528
|
+
"""
|
|
529
|
+
Insert a new workflow specification into the database
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
workflow_spec (WorkflowSpec): The workflow specification to insert
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
int | None: The ID of the inserted workflow if successful, otherwise None
|
|
536
|
+
"""
|
|
537
|
+
comment = " /* DBProxy.insert_workflow */"
|
|
538
|
+
tmp_log = self.create_tagged_logger(comment, "")
|
|
539
|
+
tmp_log.debug("start")
|
|
540
|
+
try:
|
|
541
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
542
|
+
# sql to insert workflow
|
|
543
|
+
workflow_spec.creation_time = naive_utcnow()
|
|
544
|
+
sql_insert = (
|
|
545
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflows ({workflow_spec.columnNames()}) "
|
|
546
|
+
f"{workflow_spec.bindValuesExpression()} "
|
|
547
|
+
f"RETURNING workflow_id INTO :new_workflow_id "
|
|
548
|
+
)
|
|
549
|
+
var_map = workflow_spec.valuesMap(useSeq=True)
|
|
550
|
+
var_map[":new_workflow_id"] = self.cur.var(varNUMBER)
|
|
551
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
552
|
+
workflow_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_workflow_id"])))
|
|
553
|
+
tmp_log.debug(f"inserted workflow_id={workflow_id}")
|
|
554
|
+
return workflow_id
|
|
555
|
+
except Exception:
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
def insert_workflow_step(self, step_spec: WFStepSpec) -> int | None:
|
|
559
|
+
"""
|
|
560
|
+
Insert a new workflow step specification into the database
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
step_spec (WFStepSpec): The workflow step specification to insert
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
int | None: The ID of the inserted workflow step if successful, otherwise None
|
|
567
|
+
"""
|
|
568
|
+
comment = " /* DBProxy.insert_workflow_step */"
|
|
569
|
+
tmp_log = self.create_tagged_logger(comment, "")
|
|
570
|
+
tmp_log.debug("start")
|
|
571
|
+
try:
|
|
572
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
573
|
+
# sql to insert workflow step
|
|
574
|
+
step_spec.creation_time = naive_utcnow()
|
|
575
|
+
sql_insert = (
|
|
576
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflow_steps ({step_spec.columnNames()}) "
|
|
577
|
+
f"{step_spec.bindValuesExpression()} "
|
|
578
|
+
f"RETURNING step_id INTO :new_step_id "
|
|
579
|
+
)
|
|
580
|
+
var_map = step_spec.valuesMap(useSeq=True)
|
|
581
|
+
var_map[":new_step_id"] = self.cur.var(varNUMBER)
|
|
582
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
583
|
+
step_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_step_id"])))
|
|
584
|
+
tmp_log.debug(f"inserted step_id={step_id}")
|
|
585
|
+
return step_id
|
|
586
|
+
except Exception:
|
|
587
|
+
return None
|
|
588
|
+
|
|
589
|
+
def insert_workflow_data(self, data_spec: WFDataSpec) -> int | None:
|
|
590
|
+
"""
|
|
591
|
+
Insert a new workflow data specification into the database
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
data_spec (WFDataSpec): The workflow data specification to insert
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
int | None: The ID of the inserted workflow data if successful, otherwise None
|
|
598
|
+
"""
|
|
599
|
+
comment = " /* DBProxy.insert_workflow_data */"
|
|
600
|
+
tmp_log = self.create_tagged_logger(comment, "")
|
|
601
|
+
tmp_log.debug("start")
|
|
602
|
+
try:
|
|
603
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
604
|
+
# sql to insert workflow data
|
|
605
|
+
data_spec.creation_time = naive_utcnow()
|
|
606
|
+
sql_insert = (
|
|
607
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflow_data ({data_spec.columnNames()}) "
|
|
608
|
+
f"{data_spec.bindValuesExpression()} "
|
|
609
|
+
f"RETURNING data_id INTO :new_data_id "
|
|
610
|
+
)
|
|
611
|
+
var_map = data_spec.valuesMap(useSeq=True)
|
|
612
|
+
var_map[":new_data_id"] = self.cur.var(varNUMBER)
|
|
613
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
614
|
+
data_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_data_id"])))
|
|
615
|
+
tmp_log.debug(f"inserted data_id={data_id}")
|
|
616
|
+
return data_id
|
|
617
|
+
except Exception:
|
|
618
|
+
return None
|
|
619
|
+
|
|
620
|
+
def update_workflow(self, workflow_spec: WorkflowSpec) -> WorkflowSpec | None:
|
|
621
|
+
"""
|
|
622
|
+
Update a workflow specification in the database
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
workflow_spec (WorkflowSpec): The workflow specification to update
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
WorkflowSpec | None: The updated workflow specification if successful, otherwise None
|
|
629
|
+
"""
|
|
630
|
+
comment = " /* DBProxy.update_workflow */"
|
|
631
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_spec.workflow_id}")
|
|
632
|
+
tmp_log.debug("start")
|
|
633
|
+
try:
|
|
634
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
635
|
+
# sql to update workflow
|
|
636
|
+
workflow_spec.modification_time = naive_utcnow()
|
|
637
|
+
sql_update = (
|
|
638
|
+
f"UPDATE {panda_config.schemaJEDI}.workflows " f"SET {workflow_spec.bindUpdateChangesExpression()} " "WHERE workflow_id=:workflow_id "
|
|
639
|
+
)
|
|
640
|
+
var_map = workflow_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
641
|
+
var_map[":workflow_id"] = workflow_spec.workflow_id
|
|
642
|
+
cur.execute(sql_update + comment, var_map)
|
|
643
|
+
tmp_log.debug(f"updated {workflow_spec.bindUpdateChangesExpression()}")
|
|
644
|
+
return workflow_spec
|
|
645
|
+
except Exception:
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
def update_workflow_step(self, step_spec: WFStepSpec) -> WFStepSpec | None:
|
|
649
|
+
"""
|
|
650
|
+
Update a workflow step specification in the database
|
|
651
|
+
|
|
652
|
+
Args:
|
|
653
|
+
step_spec (WFStepSpec): The workflow step specification to update
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
WFStepSpec | None: The updated workflow step specification if successful, otherwise None
|
|
657
|
+
"""
|
|
658
|
+
comment = " /* DBProxy.update_workflow_step */"
|
|
659
|
+
tmp_log = self.create_tagged_logger(comment, f"step_id={step_spec.step_id}")
|
|
660
|
+
tmp_log.debug("start")
|
|
661
|
+
try:
|
|
662
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
663
|
+
# sql to update workflow step
|
|
664
|
+
step_spec.modification_time = naive_utcnow()
|
|
665
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.workflow_steps " f"SET {step_spec.bindUpdateChangesExpression()} " "WHERE step_id=:step_id "
|
|
666
|
+
var_map = step_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
667
|
+
var_map[":step_id"] = step_spec.step_id
|
|
668
|
+
cur.execute(sql_update + comment, var_map)
|
|
669
|
+
tmp_log.debug(f"updated {step_spec.bindUpdateChangesExpression()}")
|
|
670
|
+
return step_spec
|
|
671
|
+
except Exception:
|
|
672
|
+
return None
|
|
673
|
+
|
|
674
|
+
def update_workflow_data(self, data_spec: WFDataSpec) -> WFDataSpec | None:
|
|
675
|
+
"""
|
|
676
|
+
Update a workflow data specification in the database
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
data_spec (WFDataSpec): The workflow data specification to update
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
WFDataSpec | None: The updated workflow data specification if successful, otherwise None
|
|
683
|
+
"""
|
|
684
|
+
comment = " /* DBProxy.update_workflow_data */"
|
|
685
|
+
tmp_log = self.create_tagged_logger(comment, f"data_id={data_spec.data_id}")
|
|
686
|
+
tmp_log.debug("start")
|
|
687
|
+
try:
|
|
688
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
689
|
+
# sql to update workflow data
|
|
690
|
+
data_spec.modification_time = naive_utcnow()
|
|
691
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.workflow_data " f"SET {data_spec.bindUpdateChangesExpression()} " "WHERE data_id=:data_id "
|
|
692
|
+
var_map = data_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
693
|
+
var_map[":data_id"] = data_spec.data_id
|
|
694
|
+
cur.execute(sql_update + comment, var_map)
|
|
695
|
+
tmp_log.debug(f"updated {data_spec.bindUpdateChangesExpression()}")
|
|
696
|
+
return data_spec
|
|
697
|
+
except Exception:
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
def upsert_workflow_entities(
|
|
701
|
+
self,
|
|
702
|
+
workflow_id: int | None,
|
|
703
|
+
actions_dict: dict | None = None,
|
|
704
|
+
workflow_spec: WorkflowSpec | None = None,
|
|
705
|
+
step_specs: list[WFStepSpec] | None = None,
|
|
706
|
+
data_specs: list[WFDataSpec] | None = None,
|
|
707
|
+
) -> dict | None:
|
|
708
|
+
"""
|
|
709
|
+
Update or insert (if not existing) steps and data associated with a workflow within a transaction
|
|
710
|
+
|
|
711
|
+
Args:
|
|
712
|
+
workflow_id (int | None): ID of the workflow to update, or None if to insert
|
|
713
|
+
actions_dict (dict | None): Dictionary of actions (insert, update, or None) to perform on the entities (workflow, steps, data), e.g. {"workflow": None, "steps": "insert", "data": "update"}
|
|
714
|
+
workflow_spec (WorkflowSpec|None): The workflow specification to update or insert
|
|
715
|
+
step_specs (list[WFStepSpec]|None): List of workflow step specifications to update or insert
|
|
716
|
+
data_specs (list[WFDataSpec]|None): List of workflow data specifications to update or insert
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
dict | None: Dictionary containing the number of steps and data upserted, or None if an error occurred
|
|
720
|
+
"""
|
|
721
|
+
comment = " /* DBProxy.upsert_workflow_entities */"
|
|
722
|
+
# Determine actions of each entity
|
|
723
|
+
action_of_workflow = None
|
|
724
|
+
action_of_steps = None
|
|
725
|
+
action_of_data = None
|
|
726
|
+
if actions_dict:
|
|
727
|
+
if (tmp_action_of_workflow := actions_dict.get("workflow")) and workflow_spec:
|
|
728
|
+
if tmp_action_of_workflow == "insert" and workflow_id is None:
|
|
729
|
+
action_of_workflow = "insert"
|
|
730
|
+
elif tmp_action_of_workflow == "update" and workflow_id is not None and workflow_spec.workflow_id == workflow_id:
|
|
731
|
+
action_of_workflow = "update"
|
|
732
|
+
action_of_steps = actions_dict.get("steps") if (workflow_id and step_specs) else None
|
|
733
|
+
action_of_data = actions_dict.get("data") if (workflow_id and data_specs) else None
|
|
734
|
+
actions_dict = {
|
|
735
|
+
"workflow": action_of_workflow,
|
|
736
|
+
"steps": action_of_steps,
|
|
737
|
+
"data": action_of_data,
|
|
738
|
+
}
|
|
739
|
+
# log
|
|
740
|
+
tmp_log = self.create_tagged_logger(comment, f"workflow_id={workflow_id}")
|
|
741
|
+
tmp_log.debug(f"start, actions={actions_dict}")
|
|
742
|
+
# skip if no action specified
|
|
743
|
+
if not any(actions_dict.values()):
|
|
744
|
+
tmp_log.warning("no action specified; skipped")
|
|
745
|
+
return None
|
|
746
|
+
try:
|
|
747
|
+
n_steps_upserted = 0
|
|
748
|
+
n_data_upserted = 0
|
|
749
|
+
with self.transaction(tmp_log=tmp_log) as (cur, _):
|
|
750
|
+
# action for data
|
|
751
|
+
if action_of_data == "insert":
|
|
752
|
+
for data_spec in data_specs:
|
|
753
|
+
data_spec.creation_time = naive_utcnow()
|
|
754
|
+
sql_insert = (
|
|
755
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflow_data ({data_spec.columnNames()}) "
|
|
756
|
+
f"{data_spec.bindValuesExpression()} "
|
|
757
|
+
f"RETURNING data_id INTO :new_data_id "
|
|
758
|
+
)
|
|
759
|
+
var_map = data_spec.valuesMap(useSeq=True)
|
|
760
|
+
var_map[":new_data_id"] = self.cur.var(varNUMBER)
|
|
761
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
762
|
+
data_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_data_id"])))
|
|
763
|
+
data_spec.data_id = data_id
|
|
764
|
+
n_data_upserted += 1
|
|
765
|
+
tmp_log.debug(f"inserted a data workflow_id={workflow_id} data_id={data_id}")
|
|
766
|
+
elif action_of_data == "update":
|
|
767
|
+
for data_spec in data_specs:
|
|
768
|
+
data_spec.modification_time = naive_utcnow()
|
|
769
|
+
sql_update = (
|
|
770
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_data " f"SET {data_spec.bindUpdateChangesExpression()} " "WHERE data_id=:data_id "
|
|
771
|
+
)
|
|
772
|
+
var_map = data_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
773
|
+
var_map[":data_id"] = data_spec.data_id
|
|
774
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
775
|
+
n_data_upserted += 1
|
|
776
|
+
tmp_log.debug(f"updated a data workflow_id={workflow_id} data_id={data_spec.data_id}")
|
|
777
|
+
# action for steps
|
|
778
|
+
if action_of_steps == "insert":
|
|
779
|
+
for step_spec in step_specs:
|
|
780
|
+
step_spec.creation_time = naive_utcnow()
|
|
781
|
+
sql_insert = (
|
|
782
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflow_steps ({step_spec.columnNames()}) "
|
|
783
|
+
f"{step_spec.bindValuesExpression()} "
|
|
784
|
+
f"RETURNING step_id INTO :new_step_id "
|
|
785
|
+
)
|
|
786
|
+
var_map = step_spec.valuesMap(useSeq=True)
|
|
787
|
+
var_map[":new_step_id"] = self.cur.var(varNUMBER)
|
|
788
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
789
|
+
step_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_step_id"])))
|
|
790
|
+
step_spec.step_id = step_id
|
|
791
|
+
n_steps_upserted += 1
|
|
792
|
+
tmp_log.debug(f"inserted a step workflow_id={workflow_id} step_id={step_id}")
|
|
793
|
+
elif action_of_steps == "update":
|
|
794
|
+
for step_spec in step_specs:
|
|
795
|
+
step_spec.modification_time = naive_utcnow()
|
|
796
|
+
sql_update = (
|
|
797
|
+
f"UPDATE {panda_config.schemaJEDI}.workflow_steps " f"SET {step_spec.bindUpdateChangesExpression()} " "WHERE step_id=:step_id "
|
|
798
|
+
)
|
|
799
|
+
var_map = step_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
800
|
+
var_map[":step_id"] = step_spec.step_id
|
|
801
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
802
|
+
n_steps_upserted += 1
|
|
803
|
+
tmp_log.debug(f"updated a step workflow_id={workflow_id} step_id={step_spec.step_id}")
|
|
804
|
+
# action for workflow
|
|
805
|
+
if action_of_workflow == "insert":
|
|
806
|
+
workflow_spec.creation_time = naive_utcnow()
|
|
807
|
+
sql_insert = (
|
|
808
|
+
f"INSERT INTO {panda_config.schemaJEDI}.workflows ({workflow_spec.columnNames()}) "
|
|
809
|
+
f"{workflow_spec.bindValuesExpression()} "
|
|
810
|
+
f"RETURNING workflow_id INTO :new_workflow_id "
|
|
811
|
+
)
|
|
812
|
+
var_map = workflow_spec.valuesMap(useSeq=True)
|
|
813
|
+
var_map[":new_workflow_id"] = self.cur.var(varNUMBER)
|
|
814
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
815
|
+
workflow_id = int(self.getvalue_corrector(self.cur.getvalue(var_map[":new_workflow_id"])))
|
|
816
|
+
workflow_spec.workflow_id = workflow_id
|
|
817
|
+
tmp_log.debug(f"inserted a workflow workflow_id={workflow_id}")
|
|
818
|
+
elif action_of_workflow == "update":
|
|
819
|
+
workflow_spec.modification_time = naive_utcnow()
|
|
820
|
+
sql_update = (
|
|
821
|
+
f"UPDATE {panda_config.schemaJEDI}.workflows " f"SET {workflow_spec.bindUpdateChangesExpression()} " "WHERE workflow_id=:workflow_id "
|
|
822
|
+
)
|
|
823
|
+
var_map = workflow_spec.valuesMap(useSeq=False, onlyChanged=True)
|
|
824
|
+
var_map[":workflow_id"] = workflow_spec.workflow_id
|
|
825
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
826
|
+
tmp_log.debug(f"updated a workflow workflow_id={workflow_spec.workflow_id}")
|
|
827
|
+
tmp_log.debug("actions completed")
|
|
828
|
+
# Summary
|
|
829
|
+
tmp_log.debug(f"done, actions={actions_dict}, upserted workflow_id={workflow_id} with {n_steps_upserted} steps and {n_data_upserted} data")
|
|
830
|
+
return {"workflow_id": workflow_id, "steps": n_steps_upserted, "data": n_data_upserted}
|
|
831
|
+
except Exception:
|
|
832
|
+
return None
|