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,1047 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import shlex
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from idds.atlas.workflowv2.atlaslocalpandawork import ATLASLocalPandaWork
|
|
8
|
+
from idds.atlas.workflowv2.atlaspandawork import ATLASPandaWork
|
|
9
|
+
from idds.workflowv2.workflow import AndCondition, Condition, OrCondition, Workflow
|
|
10
|
+
from pandaclient import PhpoScript, PrunScript
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# extract argument value from execution string
|
|
14
|
+
def get_arg_value(arg, exec_str):
|
|
15
|
+
args = shlex.split(exec_str)
|
|
16
|
+
if arg in args:
|
|
17
|
+
return args[args.index(arg) + 1]
|
|
18
|
+
for item in args:
|
|
19
|
+
if item.startswith(arg):
|
|
20
|
+
return item.split("=")[-1]
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# merge job parameters
|
|
25
|
+
def merge_job_params(base_params, io_params):
|
|
26
|
+
new_params = []
|
|
27
|
+
# remove exec stuff from base_params
|
|
28
|
+
exec_start = False
|
|
29
|
+
end_exec = False
|
|
30
|
+
for tmp_item in base_params:
|
|
31
|
+
if tmp_item["type"] == "constant" and tmp_item["value"].startswith("-p "):
|
|
32
|
+
exec_start = True
|
|
33
|
+
continue
|
|
34
|
+
if exec_start:
|
|
35
|
+
if end_exec:
|
|
36
|
+
pass
|
|
37
|
+
elif tmp_item["type"] == "constant" and "padding" not in tmp_item:
|
|
38
|
+
end_exec = True
|
|
39
|
+
continue
|
|
40
|
+
if exec_start and not end_exec:
|
|
41
|
+
continue
|
|
42
|
+
new_params.append(tmp_item)
|
|
43
|
+
# take exec and IO stuff from io_params
|
|
44
|
+
exec_start = False
|
|
45
|
+
for tmp_item in io_params:
|
|
46
|
+
if tmp_item["type"] == "constant" and tmp_item["value"] == "__delimiter__":
|
|
47
|
+
exec_start = True
|
|
48
|
+
continue
|
|
49
|
+
# ignore archive option
|
|
50
|
+
if tmp_item["type"] == "constant" and tmp_item["value"].startswith("-a "):
|
|
51
|
+
continue
|
|
52
|
+
if not exec_start:
|
|
53
|
+
continue
|
|
54
|
+
new_params.append(tmp_item)
|
|
55
|
+
return new_params
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# DAG vertex
|
|
59
|
+
class Node(object):
|
|
60
|
+
def __init__(self, id, node_type, data, is_leaf, name):
|
|
61
|
+
self.id = id
|
|
62
|
+
self.type = node_type
|
|
63
|
+
self.data = data
|
|
64
|
+
self.is_leaf = is_leaf
|
|
65
|
+
self.is_tail = False
|
|
66
|
+
self.is_head = False
|
|
67
|
+
self.inputs = {}
|
|
68
|
+
self.outputs = {}
|
|
69
|
+
self.output_types = []
|
|
70
|
+
self.scatter = None
|
|
71
|
+
self.parents = set()
|
|
72
|
+
self.name = name
|
|
73
|
+
self.sub_nodes = set()
|
|
74
|
+
self.root_inputs = None
|
|
75
|
+
self.task_params = None
|
|
76
|
+
self.condition = None
|
|
77
|
+
self.is_workflow_output = False
|
|
78
|
+
self.loop = False
|
|
79
|
+
self.in_loop = False
|
|
80
|
+
self.upper_root_inputs = None
|
|
81
|
+
|
|
82
|
+
def add_parent(self, id):
|
|
83
|
+
self.parents.add(id)
|
|
84
|
+
|
|
85
|
+
# set real input values
|
|
86
|
+
def set_input_value(self, key, src_key, src_value):
|
|
87
|
+
# replace the value with a list of parameter names and indexes if value is a list,
|
|
88
|
+
# and src and dst are looping params
|
|
89
|
+
if isinstance(src_value, list):
|
|
90
|
+
src_loop_param_name = self.get_loop_param_name(src_key)
|
|
91
|
+
loop_params = self.get_loop_param_name(key.split("/")[-1]) is not None and src_loop_param_name is not None
|
|
92
|
+
if loop_params:
|
|
93
|
+
src_value = [{"src": src_loop_param_name, "idx": i} for i in range(len(src_value))]
|
|
94
|
+
# resolve values
|
|
95
|
+
if isinstance(self.inputs[key]["source"], list):
|
|
96
|
+
self.inputs[key].setdefault("value", copy.copy(self.inputs[key]["source"]))
|
|
97
|
+
tmp_list = []
|
|
98
|
+
for k in self.inputs[key]["value"]:
|
|
99
|
+
if k == src_key:
|
|
100
|
+
tmp_list.append(src_value)
|
|
101
|
+
else:
|
|
102
|
+
tmp_list.append(k)
|
|
103
|
+
self.inputs[key]["value"] = tmp_list
|
|
104
|
+
else:
|
|
105
|
+
self.inputs[key]["value"] = src_value
|
|
106
|
+
|
|
107
|
+
# convert inputs to dict inputs
|
|
108
|
+
def convert_dict_inputs(self, skip_suppressed=False):
|
|
109
|
+
data = {}
|
|
110
|
+
for k, v in self.inputs.items():
|
|
111
|
+
if skip_suppressed and "suppressed" in v and v["suppressed"]:
|
|
112
|
+
continue
|
|
113
|
+
y_name = k.split("/")[-1]
|
|
114
|
+
if "value" in v:
|
|
115
|
+
data[y_name] = v["value"]
|
|
116
|
+
elif "default" in v:
|
|
117
|
+
data[y_name] = v["default"]
|
|
118
|
+
else:
|
|
119
|
+
raise ReferenceError(f"{k} is not resolved")
|
|
120
|
+
return data
|
|
121
|
+
|
|
122
|
+
# convert outputs to set
|
|
123
|
+
def convert_set_outputs(self):
|
|
124
|
+
data = set()
|
|
125
|
+
for k, v in self.outputs.items():
|
|
126
|
+
if "value" in v:
|
|
127
|
+
data.add(v["value"])
|
|
128
|
+
return data
|
|
129
|
+
|
|
130
|
+
# verify
|
|
131
|
+
def verify(self):
|
|
132
|
+
if self.is_leaf:
|
|
133
|
+
dict_inputs = self.convert_dict_inputs(True)
|
|
134
|
+
# check input
|
|
135
|
+
for k, v in dict_inputs.items():
|
|
136
|
+
if v is None:
|
|
137
|
+
return False, f"{k} is unresolved"
|
|
138
|
+
# check args
|
|
139
|
+
for k in ["opt_exec", "opt_args"]:
|
|
140
|
+
test_str = dict_inputs.get(k)
|
|
141
|
+
if test_str:
|
|
142
|
+
m = re.search(r"%{[A-Z]*DS(\d+|\*)}", test_str)
|
|
143
|
+
if m:
|
|
144
|
+
return False, f"{m.group(0)} is unresolved in {k}"
|
|
145
|
+
if self.type == "prun":
|
|
146
|
+
for k in dict_inputs:
|
|
147
|
+
if k not in [
|
|
148
|
+
"opt_inDS",
|
|
149
|
+
"opt_inDsType",
|
|
150
|
+
"opt_secondaryDSs",
|
|
151
|
+
"opt_secondaryDsTypes",
|
|
152
|
+
"opt_args",
|
|
153
|
+
"opt_exec",
|
|
154
|
+
"opt_useAthenaPackages",
|
|
155
|
+
"opt_containerImage",
|
|
156
|
+
]:
|
|
157
|
+
return False, f"unknown input parameter {k} for {self.type}"
|
|
158
|
+
elif self.type in ["junction", "reana"]:
|
|
159
|
+
for k in dict_inputs:
|
|
160
|
+
if k not in [
|
|
161
|
+
"opt_inDS",
|
|
162
|
+
"opt_inDsType",
|
|
163
|
+
"opt_args",
|
|
164
|
+
"opt_exec",
|
|
165
|
+
"opt_containerImage",
|
|
166
|
+
]:
|
|
167
|
+
return False, f"unknown input parameter {k} for {self.type}"
|
|
168
|
+
elif self.type == "phpo":
|
|
169
|
+
for k in dict_inputs:
|
|
170
|
+
if k not in ["opt_trainingDS", "opt_trainingDsType", "opt_args"]:
|
|
171
|
+
return False, f"unknown input parameter {k} for {self.type}"
|
|
172
|
+
elif self.type == "gitlab":
|
|
173
|
+
for k in dict_inputs:
|
|
174
|
+
if k not in [
|
|
175
|
+
"opt_inDS",
|
|
176
|
+
"opt_args",
|
|
177
|
+
"opt_api",
|
|
178
|
+
"opt_projectID",
|
|
179
|
+
"opt_ref",
|
|
180
|
+
"opt_triggerToken",
|
|
181
|
+
"opt_accessToken",
|
|
182
|
+
"opt_site",
|
|
183
|
+
"opt_input_location",
|
|
184
|
+
]:
|
|
185
|
+
return False, f"unknown input parameter {k} for {self.type}"
|
|
186
|
+
elif self.type == "workflow":
|
|
187
|
+
reserved_params = ["i"]
|
|
188
|
+
loop_global, workflow_global = self.get_global_parameters()
|
|
189
|
+
if loop_global:
|
|
190
|
+
for k in reserved_params:
|
|
191
|
+
if k in loop_global:
|
|
192
|
+
return (
|
|
193
|
+
False,
|
|
194
|
+
f"parameter {k} cannot be used since it is reserved by the system",
|
|
195
|
+
)
|
|
196
|
+
return True, ""
|
|
197
|
+
|
|
198
|
+
# string representation
|
|
199
|
+
def __str__(self):
|
|
200
|
+
outstr = f"ID:{self.id} Name:{self.name} Type:{self.type}\n"
|
|
201
|
+
outstr += f" Parent:{','.join([str(p) for p in self.parents])}\n"
|
|
202
|
+
outstr += " Input:\n"
|
|
203
|
+
for k, v in self.convert_dict_inputs().items():
|
|
204
|
+
outstr += f" {k}: {v}\n"
|
|
205
|
+
outstr += " Output:\n"
|
|
206
|
+
for k, v in self.outputs.items():
|
|
207
|
+
if "value" in v:
|
|
208
|
+
v = v["value"]
|
|
209
|
+
else:
|
|
210
|
+
v = "NA"
|
|
211
|
+
outstr += f" {v}\n"
|
|
212
|
+
return outstr
|
|
213
|
+
|
|
214
|
+
# short description
|
|
215
|
+
def short_desc(self):
|
|
216
|
+
return f"ID:{self.id} Name:{self.name} Type:{self.type}"
|
|
217
|
+
|
|
218
|
+
# resolve workload-specific parameters
|
|
219
|
+
def resolve_params(self, task_template=None, id_map=None, workflow=None):
|
|
220
|
+
if self.type in ["prun", "junction", "reana"]:
|
|
221
|
+
dict_inputs = self.convert_dict_inputs()
|
|
222
|
+
if "opt_secondaryDSs" in dict_inputs:
|
|
223
|
+
# look for secondaryDsTypes if missing
|
|
224
|
+
if "opt_secondaryDsTypes" not in dict_inputs:
|
|
225
|
+
dict_inputs["opt_secondaryDsTypes"] = []
|
|
226
|
+
for ds_name in dict_inputs["opt_secondaryDSs"]:
|
|
227
|
+
added = False
|
|
228
|
+
for pid in self.parents:
|
|
229
|
+
parent_node = id_map[pid]
|
|
230
|
+
if ds_name in parent_node.convert_set_outputs():
|
|
231
|
+
dict_inputs["opt_secondaryDsTypes"].append(parent_node.output_types[0])
|
|
232
|
+
added = True
|
|
233
|
+
break
|
|
234
|
+
if not added:
|
|
235
|
+
# use None if not found
|
|
236
|
+
dict_inputs["opt_secondaryDsTypes"].append(None)
|
|
237
|
+
# resolve secondary dataset names
|
|
238
|
+
idx = 1
|
|
239
|
+
list_sec_ds = []
|
|
240
|
+
for ds_name, ds_type in zip(dict_inputs["opt_secondaryDSs"], dict_inputs["opt_secondaryDsTypes"]):
|
|
241
|
+
if ds_type and "*" in ds_type:
|
|
242
|
+
ds_type = ds_type.replace("*", "XYZ")
|
|
243
|
+
ds_type += ".tgz"
|
|
244
|
+
src = f"%{{SECDS{idx}}}"
|
|
245
|
+
if ds_type:
|
|
246
|
+
dst = f"{ds_name}_{ds_type}/"
|
|
247
|
+
else:
|
|
248
|
+
dst = f"{ds_name}/"
|
|
249
|
+
dict_inputs["opt_exec"] = re.sub(src, dst, dict_inputs["opt_exec"])
|
|
250
|
+
dict_inputs["opt_args"] = re.sub(src, dst, dict_inputs["opt_args"])
|
|
251
|
+
idx += 1
|
|
252
|
+
list_sec_ds.append(src)
|
|
253
|
+
if list_sec_ds:
|
|
254
|
+
src = r"%{SECDS\*}"
|
|
255
|
+
if "opt_exec" in dict_inputs:
|
|
256
|
+
dict_inputs["opt_exec"] = re.sub(src, ",".join(list_sec_ds), dict_inputs["opt_exec"])
|
|
257
|
+
if "opt_args" in dict_inputs:
|
|
258
|
+
dict_inputs["opt_args"] = re.sub(src, ",".join(list_sec_ds), dict_inputs["opt_args"])
|
|
259
|
+
for k, v in self.inputs.items():
|
|
260
|
+
if k.endswith("opt_exec"):
|
|
261
|
+
v["value"] = dict_inputs["opt_exec"]
|
|
262
|
+
elif k.endswith("opt_args"):
|
|
263
|
+
v["value"] = dict_inputs["opt_args"]
|
|
264
|
+
if self.is_leaf and task_template:
|
|
265
|
+
self.task_params = self.make_task_params(task_template, id_map, workflow)
|
|
266
|
+
[n.resolve_params(task_template, id_map, self) for n in self.sub_nodes]
|
|
267
|
+
|
|
268
|
+
# create task params
|
|
269
|
+
def make_task_params(self, task_template, id_map, workflow_node):
|
|
270
|
+
# task name
|
|
271
|
+
for k, v in self.outputs.items():
|
|
272
|
+
task_name = v["value"]
|
|
273
|
+
break
|
|
274
|
+
if self.type in ["prun", "junction", "reana"]:
|
|
275
|
+
dict_inputs = self.convert_dict_inputs(skip_suppressed=True)
|
|
276
|
+
# check type
|
|
277
|
+
use_athena = False
|
|
278
|
+
if "opt_useAthenaPackages" in dict_inputs and dict_inputs["opt_useAthenaPackages"] and self.type != "reana":
|
|
279
|
+
use_athena = True
|
|
280
|
+
container_image = None
|
|
281
|
+
if "opt_containerImage" in dict_inputs and dict_inputs["opt_containerImage"]:
|
|
282
|
+
container_image = dict_inputs["opt_containerImage"]
|
|
283
|
+
if use_athena:
|
|
284
|
+
task_params = copy.deepcopy(task_template["athena"])
|
|
285
|
+
else:
|
|
286
|
+
task_params = copy.deepcopy(task_template["container"])
|
|
287
|
+
task_params["taskName"] = task_name
|
|
288
|
+
# cli params
|
|
289
|
+
com = ["prun"]
|
|
290
|
+
if self.type == "junction":
|
|
291
|
+
# add default output for junction
|
|
292
|
+
if "opt_args" not in dict_inputs:
|
|
293
|
+
dict_inputs["opt_args"] = ""
|
|
294
|
+
results_json = "results.json"
|
|
295
|
+
if "--outputs" not in dict_inputs["opt_args"]:
|
|
296
|
+
dict_inputs["opt_args"] += f" --outputs {results_json}"
|
|
297
|
+
else:
|
|
298
|
+
m = re.search("(--outputs)( +|=)([^ ]+)", dict_inputs["opt_args"])
|
|
299
|
+
if results_json not in m.group(3):
|
|
300
|
+
tmp_dst = m.group(1) + "=" + m.group(3) + "," + results_json
|
|
301
|
+
dict_inputs["opt_args"] = re.sub(m.group(0), tmp_dst, dict_inputs["opt_args"])
|
|
302
|
+
com += shlex.split(dict_inputs["opt_args"])
|
|
303
|
+
if "opt_inDS" in dict_inputs and dict_inputs["opt_inDS"]:
|
|
304
|
+
list_in_ds = self.get_input_ds_list(dict_inputs, id_map)
|
|
305
|
+
if self.type not in ["reana"]:
|
|
306
|
+
in_ds_str = ",".join(list_in_ds)
|
|
307
|
+
com += ["--inDS", in_ds_str, "--notExpandInDS", "--notExpandSecDSs"]
|
|
308
|
+
if self.type in ["junction"]:
|
|
309
|
+
com += ["--forceStaged", "--forceStagedSecondary"]
|
|
310
|
+
if self.type in ["prun", "junction", "reana"]:
|
|
311
|
+
# replace placeholders in opt_exec and opt_args
|
|
312
|
+
for idx, dst in enumerate(list_in_ds):
|
|
313
|
+
src = f"%{{DS{idx + 1}}}"
|
|
314
|
+
if "opt_exec" in dict_inputs:
|
|
315
|
+
dict_inputs["opt_exec"] = re.sub(src, dst, dict_inputs["opt_exec"])
|
|
316
|
+
if "opt_args" in dict_inputs:
|
|
317
|
+
dict_inputs["opt_args"] = re.sub(src, dst, dict_inputs["opt_args"])
|
|
318
|
+
if list_in_ds:
|
|
319
|
+
src = r"%{DS\*}"
|
|
320
|
+
if "opt_exec" in dict_inputs:
|
|
321
|
+
dict_inputs["opt_exec"] = re.sub(src, ",".join(list_in_ds), dict_inputs["opt_exec"])
|
|
322
|
+
if "opt_args" in dict_inputs:
|
|
323
|
+
dict_inputs["opt_args"] = re.sub(src, ",".join(list_in_ds), dict_inputs["opt_args"])
|
|
324
|
+
for k, v in self.inputs.items():
|
|
325
|
+
if k.endswith("opt_exec"):
|
|
326
|
+
v["value"] = dict_inputs["opt_exec"]
|
|
327
|
+
elif k.endswith("opt_args"):
|
|
328
|
+
v["value"] = dict_inputs["opt_args"]
|
|
329
|
+
# global parameters
|
|
330
|
+
if workflow_node:
|
|
331
|
+
tmp_global, tmp_workflow_global = workflow_node.get_global_parameters()
|
|
332
|
+
src_dst_list = []
|
|
333
|
+
# looping globals
|
|
334
|
+
if tmp_global:
|
|
335
|
+
for k in tmp_global:
|
|
336
|
+
tmp_src = f"%{{{k}}}"
|
|
337
|
+
tmp_dst = f"___idds___user_{k}___"
|
|
338
|
+
src_dst_list.append((tmp_src, tmp_dst))
|
|
339
|
+
# workflow globls
|
|
340
|
+
if tmp_workflow_global:
|
|
341
|
+
for k, v in tmp_workflow_global.items():
|
|
342
|
+
tmp_src = f"%{{{k}}}"
|
|
343
|
+
tmp_dst = f"{v}"
|
|
344
|
+
src_dst_list.append((tmp_src, tmp_dst))
|
|
345
|
+
# iteration count
|
|
346
|
+
tmp_src = "%{i}"
|
|
347
|
+
tmp_dst = "___idds___num_run___"
|
|
348
|
+
src_dst_list.append((tmp_src, tmp_dst))
|
|
349
|
+
# replace
|
|
350
|
+
for tmp_src, tmp_dst in src_dst_list:
|
|
351
|
+
if "opt_exec" in dict_inputs:
|
|
352
|
+
dict_inputs["opt_exec"] = re.sub(tmp_src, tmp_dst, dict_inputs["opt_exec"])
|
|
353
|
+
if "opt_args" in dict_inputs:
|
|
354
|
+
dict_inputs["opt_args"] = re.sub(tmp_src, tmp_dst, dict_inputs["opt_args"])
|
|
355
|
+
com += ["--exec", dict_inputs["opt_exec"]]
|
|
356
|
+
com += ["--outDS", task_name]
|
|
357
|
+
if container_image:
|
|
358
|
+
com += ["--containerImage", container_image]
|
|
359
|
+
parse_com = copy.copy(com[1:])
|
|
360
|
+
else:
|
|
361
|
+
# add dummy container to keep build step consistent
|
|
362
|
+
parse_com = copy.copy(com[1:])
|
|
363
|
+
parse_com += ["--containerImage", None]
|
|
364
|
+
# force a writable temp base for dry parsing regardless of process cwd
|
|
365
|
+
parse_com += ["--tmpDir", tempfile.gettempdir()]
|
|
366
|
+
athena_tag = False
|
|
367
|
+
if use_athena:
|
|
368
|
+
com += ["--useAthenaPackages"]
|
|
369
|
+
athena_tag = "--athenaTag" in com
|
|
370
|
+
# add cmtConfig
|
|
371
|
+
if athena_tag and "--cmtConfig" not in parse_com:
|
|
372
|
+
parse_com += [
|
|
373
|
+
"--cmtConfig",
|
|
374
|
+
task_params["architecture"].split("@")[0],
|
|
375
|
+
]
|
|
376
|
+
# parse args without setting --useAthenaPackages since it requires real Athena runtime
|
|
377
|
+
parsed_params = PrunScript.main(True, parse_com, dry_mode=True)
|
|
378
|
+
task_params["cliParams"] = " ".join(shlex.quote(x) for x in com)
|
|
379
|
+
# set parsed parameters
|
|
380
|
+
for p_key, p_value in parsed_params.items():
|
|
381
|
+
if p_key in ["buildSpec"]:
|
|
382
|
+
continue
|
|
383
|
+
if p_key not in task_params or p_key in [
|
|
384
|
+
"log",
|
|
385
|
+
"container_name",
|
|
386
|
+
"multiStepExec",
|
|
387
|
+
"site",
|
|
388
|
+
"excludedSite",
|
|
389
|
+
"includedSite",
|
|
390
|
+
]:
|
|
391
|
+
task_params[p_key] = p_value
|
|
392
|
+
elif p_key == "architecture":
|
|
393
|
+
task_params[p_key] = p_value
|
|
394
|
+
if not container_image:
|
|
395
|
+
if task_params[p_key] is None:
|
|
396
|
+
task_params[p_key] = ""
|
|
397
|
+
if "@" not in task_params[p_key] and "basePlatform" in task_params:
|
|
398
|
+
task_params[p_key] = f"{task_params[p_key]}@{task_params['basePlatform']}"
|
|
399
|
+
elif athena_tag:
|
|
400
|
+
if p_key in ["transUses", "transHome"]:
|
|
401
|
+
task_params[p_key] = p_value
|
|
402
|
+
# merge job params
|
|
403
|
+
task_params["jobParameters"] = merge_job_params(task_params["jobParameters"], parsed_params["jobParameters"])
|
|
404
|
+
# outputs
|
|
405
|
+
for tmp_item in task_params["jobParameters"]:
|
|
406
|
+
if tmp_item["type"] == "template" and tmp_item["param_type"] == "output":
|
|
407
|
+
if tmp_item["value"].startswith("regex|"):
|
|
408
|
+
self.output_types.append(re.search(r"_([^_]+)/$", tmp_item["dataset"]).group(1))
|
|
409
|
+
else:
|
|
410
|
+
self.output_types.append(re.search(r"}\.(.+)$", tmp_item["value"]).group(1))
|
|
411
|
+
# add a dummy output if empty. this is to allow association to downstream steps which is described through outputs
|
|
412
|
+
if not self.output_types:
|
|
413
|
+
self.output_types.append("dummy")
|
|
414
|
+
# container
|
|
415
|
+
if not container_image:
|
|
416
|
+
if "container_name" in task_params:
|
|
417
|
+
del task_params["container_name"]
|
|
418
|
+
if "multiStepExec" in task_params:
|
|
419
|
+
del task_params["multiStepExec"]
|
|
420
|
+
if "basePlatform" in task_params:
|
|
421
|
+
del task_params["basePlatform"]
|
|
422
|
+
# no build
|
|
423
|
+
if use_athena and "--noBuild" in parse_com:
|
|
424
|
+
for tmp_item in task_params["jobParameters"]:
|
|
425
|
+
if tmp_item["type"] == "constant" and tmp_item["value"] == "-l ${LIB}":
|
|
426
|
+
tmp_item["value"] = f"-a {task_params['buildSpec']['archiveName']}"
|
|
427
|
+
del task_params["buildSpec"]
|
|
428
|
+
# parent
|
|
429
|
+
# if self.parents and len(self.parents) == 1:
|
|
430
|
+
# task_params["noWaitParent"] = True
|
|
431
|
+
# task_params["parentTaskName"] = id_map[list(self.parents)[0]].task_params["taskName"]
|
|
432
|
+
# notification
|
|
433
|
+
if not self.is_workflow_output:
|
|
434
|
+
task_params["noEmail"] = True
|
|
435
|
+
# use instant PQs
|
|
436
|
+
if self.type in ["junction", "reana"]:
|
|
437
|
+
task_params["runOnInstant"] = True
|
|
438
|
+
# return
|
|
439
|
+
return task_params
|
|
440
|
+
elif self.type == "phpo":
|
|
441
|
+
dict_inputs = self.convert_dict_inputs(skip_suppressed=True)
|
|
442
|
+
# extract source and base URL
|
|
443
|
+
source_url = task_template["container"]["sourceURL"]
|
|
444
|
+
for tmp_item in task_template["container"]["jobParameters"]:
|
|
445
|
+
if tmp_item["type"] == "constant" and tmp_item["value"].startswith("-a "):
|
|
446
|
+
source_name = tmp_item["value"].split()[-1]
|
|
447
|
+
# cli params
|
|
448
|
+
com = shlex.split(dict_inputs["opt_args"])
|
|
449
|
+
if "opt_trainingDS" in dict_inputs and dict_inputs["opt_trainingDS"]:
|
|
450
|
+
if "opt_trainingDsType" not in dict_inputs or not dict_inputs["opt_trainingDsType"]:
|
|
451
|
+
in_ds_suffix = None
|
|
452
|
+
for parent_id in self.parents:
|
|
453
|
+
parent_node = id_map[parent_id]
|
|
454
|
+
if dict_inputs["opt_trainingDS"] in parent_node.convert_set_outputs():
|
|
455
|
+
in_ds_suffix = parent_node.output_types[0]
|
|
456
|
+
break
|
|
457
|
+
else:
|
|
458
|
+
in_ds_suffix = dict_inputs["opt_inDsType"]
|
|
459
|
+
in_ds_str = f"{dict_inputs['opt_trainingDS']}_{in_ds_suffix}/"
|
|
460
|
+
com += ["--trainingDS", in_ds_str]
|
|
461
|
+
com += ["--outDS", task_name]
|
|
462
|
+
# get task params
|
|
463
|
+
task_params = PhpoScript.main(True, com, dry_mode=True)
|
|
464
|
+
# change sandbox
|
|
465
|
+
new_job_params = []
|
|
466
|
+
for tmp_item in task_params["jobParameters"]:
|
|
467
|
+
if tmp_item["type"] == "constant" and tmp_item["value"].startswith("-a "):
|
|
468
|
+
tmp_item["value"] = f"-a {source_name} --sourceURL {source_url}"
|
|
469
|
+
new_job_params.append(tmp_item)
|
|
470
|
+
task_params["jobParameters"] = new_job_params
|
|
471
|
+
# return
|
|
472
|
+
return task_params
|
|
473
|
+
elif self.type == "gitlab":
|
|
474
|
+
dict_inputs = self.convert_dict_inputs(skip_suppressed=True)
|
|
475
|
+
list_in_ds = self.get_input_ds_list(dict_inputs, id_map)
|
|
476
|
+
task_params = copy.copy(task_template["container"])
|
|
477
|
+
task_params["taskName"] = task_name
|
|
478
|
+
task_params["noInput"] = True
|
|
479
|
+
task_params["nEventsPerJob"] = 1
|
|
480
|
+
task_params["nEvents"] = 1
|
|
481
|
+
task_params["processingType"] = re.sub(r"-[^-]+$", "-gitlab", task_params["processingType"])
|
|
482
|
+
task_params["useSecrets"] = True
|
|
483
|
+
task_params["site"] = dict_inputs["opt_site"]
|
|
484
|
+
task_params["cliParams"] = ""
|
|
485
|
+
task_params["log"]["container"] = task_params["log"]["dataset"] = f"{task_name}.log/"
|
|
486
|
+
# set gitlab parameters
|
|
487
|
+
task_params["jobParameters"] = [
|
|
488
|
+
{
|
|
489
|
+
"type": "constant",
|
|
490
|
+
"value": json.dumps(
|
|
491
|
+
{
|
|
492
|
+
"project_api": dict_inputs["opt_api"],
|
|
493
|
+
"project_id": int(dict_inputs["opt_projectID"]),
|
|
494
|
+
"ref": dict_inputs["opt_ref"],
|
|
495
|
+
"trigger_token": dict_inputs["opt_triggerToken"],
|
|
496
|
+
"access_token": dict_inputs["opt_accessToken"],
|
|
497
|
+
"input_datasets": ",".join(list_in_ds),
|
|
498
|
+
"input_location": dict_inputs.get("opt_input_location"),
|
|
499
|
+
}
|
|
500
|
+
),
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
del task_params["container_name"]
|
|
505
|
+
del task_params["multiStepExec"]
|
|
506
|
+
return task_params
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
# get global parameters in the workflow
|
|
510
|
+
def get_global_parameters(self):
|
|
511
|
+
if self.is_leaf:
|
|
512
|
+
root_inputs = self.upper_root_inputs
|
|
513
|
+
else:
|
|
514
|
+
root_inputs = self.root_inputs
|
|
515
|
+
if root_inputs is None:
|
|
516
|
+
return None, None
|
|
517
|
+
loop_params = {}
|
|
518
|
+
workflow_params = {}
|
|
519
|
+
for k, v in root_inputs.items():
|
|
520
|
+
m = self.get_loop_param_name(k)
|
|
521
|
+
if m:
|
|
522
|
+
loop_params[m] = v
|
|
523
|
+
else:
|
|
524
|
+
param = k.split("#")[-1]
|
|
525
|
+
workflow_params[param] = v
|
|
526
|
+
return loop_params, workflow_params
|
|
527
|
+
|
|
528
|
+
# get all sub node IDs
|
|
529
|
+
def get_all_sub_node_ids(self, all_ids=None):
|
|
530
|
+
if all_ids is None:
|
|
531
|
+
all_ids = set()
|
|
532
|
+
all_ids.add(self.id)
|
|
533
|
+
for sub_node in self.sub_nodes:
|
|
534
|
+
all_ids.add(sub_node.id)
|
|
535
|
+
if not sub_node.is_leaf:
|
|
536
|
+
sub_node.get_all_sub_node_ids(all_ids)
|
|
537
|
+
return all_ids
|
|
538
|
+
|
|
539
|
+
# get loop param name
|
|
540
|
+
def get_loop_param_name(self, k):
|
|
541
|
+
param = k.split("#")[-1]
|
|
542
|
+
m = re.search(r"^param_(.+)", param)
|
|
543
|
+
if m:
|
|
544
|
+
return m.group(1)
|
|
545
|
+
return None
|
|
546
|
+
|
|
547
|
+
# def get input dataset list
|
|
548
|
+
def get_input_ds_list(self, dict_inputs, id_map):
|
|
549
|
+
if "opt_inDS" not in dict_inputs:
|
|
550
|
+
return []
|
|
551
|
+
if isinstance(dict_inputs["opt_inDS"], list):
|
|
552
|
+
is_list_in_ds = True
|
|
553
|
+
else:
|
|
554
|
+
is_list_in_ds = False
|
|
555
|
+
if "opt_inDsType" not in dict_inputs or not dict_inputs["opt_inDsType"]:
|
|
556
|
+
if is_list_in_ds:
|
|
557
|
+
in_ds_suffix = []
|
|
558
|
+
in_ds_list = dict_inputs["opt_inDS"]
|
|
559
|
+
else:
|
|
560
|
+
in_ds_suffix = None
|
|
561
|
+
in_ds_list = [dict_inputs["opt_inDS"]]
|
|
562
|
+
for tmp_in_ds in in_ds_list:
|
|
563
|
+
for parent_id in self.parents:
|
|
564
|
+
parent_node = id_map[parent_id]
|
|
565
|
+
if tmp_in_ds in parent_node.convert_set_outputs():
|
|
566
|
+
if is_list_in_ds:
|
|
567
|
+
in_ds_suffix.append(parent_node.output_types[0])
|
|
568
|
+
else:
|
|
569
|
+
in_ds_suffix = parent_node.output_types[0]
|
|
570
|
+
break
|
|
571
|
+
else:
|
|
572
|
+
in_ds_suffix = dict_inputs["opt_inDsType"]
|
|
573
|
+
if "*" in in_ds_suffix:
|
|
574
|
+
in_ds_suffix = in_ds_suffix.replace("*", "XYZ") + ".tgz"
|
|
575
|
+
if is_list_in_ds:
|
|
576
|
+
list_in_ds = [f"{s1}_{s2}/" if s2 else s1 for s1, s2 in zip(dict_inputs["opt_inDS"], in_ds_suffix)]
|
|
577
|
+
else:
|
|
578
|
+
list_in_ds = [f"{dict_inputs['opt_inDS']}_{in_ds_suffix}/" if in_ds_suffix else dict_inputs["opt_inDS"]]
|
|
579
|
+
return list_in_ds
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# dump nodes
|
|
583
|
+
def dump_nodes(node_list, dump_str=None, only_leaves=False):
|
|
584
|
+
if dump_str is None:
|
|
585
|
+
dump_str = "\n"
|
|
586
|
+
for node in node_list:
|
|
587
|
+
if node.is_leaf:
|
|
588
|
+
dump_str += f"{node}"
|
|
589
|
+
if node.task_params is not None:
|
|
590
|
+
dump_str += json.dumps(node.task_params, indent=4, sort_keys=True)
|
|
591
|
+
dump_str += "\n\n"
|
|
592
|
+
else:
|
|
593
|
+
if not only_leaves:
|
|
594
|
+
dump_str += f"{node}\n"
|
|
595
|
+
dump_str = dump_nodes(node.sub_nodes, dump_str, only_leaves)
|
|
596
|
+
return dump_str
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
# get id map
|
|
600
|
+
def get_node_id_map(node_list, id_map=None):
|
|
601
|
+
if id_map is None:
|
|
602
|
+
id_map = {}
|
|
603
|
+
for node in node_list:
|
|
604
|
+
id_map[node.id] = node
|
|
605
|
+
if node.sub_nodes:
|
|
606
|
+
id_map = get_node_id_map(node.sub_nodes, id_map)
|
|
607
|
+
return id_map
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
# get all parents
|
|
611
|
+
def get_all_parents(node_list, all_parents=None):
|
|
612
|
+
if all_parents is None:
|
|
613
|
+
all_parents = set()
|
|
614
|
+
for node in node_list:
|
|
615
|
+
all_parents |= node.parents
|
|
616
|
+
if node.sub_nodes:
|
|
617
|
+
all_parents = get_all_parents(node.sub_nodes, all_parents)
|
|
618
|
+
return all_parents
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
# set workflow outputs
|
|
622
|
+
def set_workflow_outputs(node_list, all_parents=None):
|
|
623
|
+
if all_parents is None:
|
|
624
|
+
all_parents = get_all_parents(node_list)
|
|
625
|
+
for node in node_list:
|
|
626
|
+
if node.is_leaf and node.id not in all_parents:
|
|
627
|
+
node.is_workflow_output = True
|
|
628
|
+
if node.sub_nodes:
|
|
629
|
+
set_workflow_outputs(node.sub_nodes, all_parents)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
# convert parameter names to parent IDs
|
|
633
|
+
def convert_params_in_condition_to_parent_ids(condition_item, input_data, id_map):
|
|
634
|
+
for item in ["left", "right"]:
|
|
635
|
+
param = getattr(condition_item, item)
|
|
636
|
+
if isinstance(param, str):
|
|
637
|
+
m = re.search(r"^[^\[]+\[(\d+)\]", param)
|
|
638
|
+
if m:
|
|
639
|
+
param = param.split("[")[0]
|
|
640
|
+
idx = int(m.group(1))
|
|
641
|
+
else:
|
|
642
|
+
idx = None
|
|
643
|
+
isOK = False
|
|
644
|
+
for tmp_name, tmp_data in input_data.items():
|
|
645
|
+
if param == tmp_name.split("/")[-1]:
|
|
646
|
+
isOK = True
|
|
647
|
+
if isinstance(tmp_data["parent_id"], list):
|
|
648
|
+
if idx is not None:
|
|
649
|
+
if idx < 0 or idx >= len(tmp_data["parent_id"]):
|
|
650
|
+
raise IndexError(f"index {idx} is out of bounds for parameter {param} with {len(tmp_data['parent_id'])} parents")
|
|
651
|
+
parent_id = tmp_data["parent_id"][idx]
|
|
652
|
+
if parent_id not in id_map:
|
|
653
|
+
raise ReferenceError(f"unresolved parent_id {parent_id} for parameter {param}[{idx}]")
|
|
654
|
+
setattr(condition_item, item, id_map[parent_id])
|
|
655
|
+
else:
|
|
656
|
+
resolved_parent_ids = set()
|
|
657
|
+
for parent_id in tmp_data["parent_id"]:
|
|
658
|
+
if parent_id not in id_map:
|
|
659
|
+
raise ReferenceError(f"unresolved parent_id {parent_id} for parameter {param}")
|
|
660
|
+
resolved_parent_ids |= id_map[parent_id]
|
|
661
|
+
setattr(condition_item, item, list(resolved_parent_ids))
|
|
662
|
+
else:
|
|
663
|
+
if tmp_data["parent_id"] not in id_map:
|
|
664
|
+
raise ReferenceError(f"unresolved parent_id {tmp_data['parent_id']} for parameter {param}")
|
|
665
|
+
setattr(condition_item, item, id_map[tmp_data["parent_id"]])
|
|
666
|
+
break
|
|
667
|
+
if not isOK:
|
|
668
|
+
raise ReferenceError(f"unresolved parameter {param} in the condition string")
|
|
669
|
+
elif isinstance(param, ConditionItem):
|
|
670
|
+
convert_params_in_condition_to_parent_ids(param, input_data, id_map)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
# resolve nodes
|
|
674
|
+
def resolve_nodes(node_list, root_inputs, data, serial_id, parent_ids, out_ds_name, log_stream):
|
|
675
|
+
for k in root_inputs:
|
|
676
|
+
kk = k.split("#")[-1]
|
|
677
|
+
if kk in data:
|
|
678
|
+
root_inputs[k] = data[kk]
|
|
679
|
+
tmp_to_real_id_map = {}
|
|
680
|
+
resolved_map = {}
|
|
681
|
+
# map of object identity to original temporary node ID used in resolved_map keys
|
|
682
|
+
node_key_map = {}
|
|
683
|
+
all_nodes = []
|
|
684
|
+
for node in node_list:
|
|
685
|
+
# resolve input
|
|
686
|
+
for tmp_name, tmp_data in node.inputs.items():
|
|
687
|
+
if not tmp_data["source"]:
|
|
688
|
+
continue
|
|
689
|
+
if isinstance(tmp_data["source"], list):
|
|
690
|
+
tmp_sources = tmp_data["source"]
|
|
691
|
+
if "parent_id" in tmp_data:
|
|
692
|
+
# Make a copy to avoid mutating the original list stored in node.inputs
|
|
693
|
+
tmp_parent_ids = list(tmp_data["parent_id"])
|
|
694
|
+
tmp_parent_ids += [None] * (len(tmp_sources) - len(tmp_parent_ids))
|
|
695
|
+
else:
|
|
696
|
+
tmp_parent_ids = [None] * len(tmp_sources)
|
|
697
|
+
else:
|
|
698
|
+
tmp_sources = [tmp_data["source"]]
|
|
699
|
+
if "parent_id" in tmp_data:
|
|
700
|
+
tmp_parent_ids = [tmp_data["parent_id"]]
|
|
701
|
+
else:
|
|
702
|
+
tmp_parent_ids = [None] * len(tmp_sources)
|
|
703
|
+
for tmp_source, tmp_parent_id in zip(tmp_sources, tmp_parent_ids):
|
|
704
|
+
isOK = False
|
|
705
|
+
# check root input
|
|
706
|
+
if tmp_source in root_inputs:
|
|
707
|
+
node.is_head = True
|
|
708
|
+
node.set_input_value(tmp_name, tmp_source, root_inputs[tmp_source])
|
|
709
|
+
continue
|
|
710
|
+
# check parent output
|
|
711
|
+
for i in node.parents:
|
|
712
|
+
for r_node in resolved_map[i]:
|
|
713
|
+
if tmp_source in r_node.outputs:
|
|
714
|
+
node.set_input_value(
|
|
715
|
+
tmp_name,
|
|
716
|
+
tmp_source,
|
|
717
|
+
r_node.outputs[tmp_source]["value"],
|
|
718
|
+
)
|
|
719
|
+
isOK = True
|
|
720
|
+
break
|
|
721
|
+
if isOK:
|
|
722
|
+
break
|
|
723
|
+
if isOK:
|
|
724
|
+
continue
|
|
725
|
+
# check resolved parent outputs
|
|
726
|
+
if tmp_parent_id is not None:
|
|
727
|
+
values = [list(r_node.outputs.values())[0]["value"] for r_node in resolved_map[tmp_parent_id]]
|
|
728
|
+
if len(values) == 1:
|
|
729
|
+
values = values[0]
|
|
730
|
+
node.set_input_value(tmp_name, tmp_source, values)
|
|
731
|
+
continue
|
|
732
|
+
# scatter
|
|
733
|
+
if node.scatter:
|
|
734
|
+
# resolve scattered parameters
|
|
735
|
+
scatters = None
|
|
736
|
+
sc_nodes = []
|
|
737
|
+
for item in node.scatter:
|
|
738
|
+
if scatters is None:
|
|
739
|
+
scatters = [{item: v} for v in node.inputs[item]["value"]]
|
|
740
|
+
else:
|
|
741
|
+
[i.update({item: v}) for i, v in zip(scatters, node.inputs[item]["value"])]
|
|
742
|
+
for idx, item in enumerate(scatters):
|
|
743
|
+
sc_node = copy.deepcopy(node)
|
|
744
|
+
for k, v in item.items():
|
|
745
|
+
sc_node.inputs[k]["value"] = v
|
|
746
|
+
for tmp_node in sc_node.sub_nodes:
|
|
747
|
+
tmp_node.scatter_index = idx
|
|
748
|
+
tmp_node.upper_root_inputs = sc_node.root_inputs
|
|
749
|
+
sc_nodes.append(sc_node)
|
|
750
|
+
else:
|
|
751
|
+
sc_nodes = [node]
|
|
752
|
+
# loop over scattered nodes
|
|
753
|
+
for sc_node in sc_nodes:
|
|
754
|
+
original_node_id = sc_node.id
|
|
755
|
+
all_nodes.append(sc_node)
|
|
756
|
+
node_key_map[id(sc_node)] = original_node_id
|
|
757
|
+
# set real node ID
|
|
758
|
+
resolved_map.setdefault(original_node_id, [])
|
|
759
|
+
tmp_to_real_id_map.setdefault(original_node_id, set())
|
|
760
|
+
# resolve parents
|
|
761
|
+
real_parens = set()
|
|
762
|
+
for i in sc_node.parents:
|
|
763
|
+
real_parens |= tmp_to_real_id_map[i]
|
|
764
|
+
sc_node.parents = real_parens
|
|
765
|
+
if sc_node.is_head:
|
|
766
|
+
sc_node.parents |= parent_ids
|
|
767
|
+
if sc_node.is_leaf:
|
|
768
|
+
resolved_map[original_node_id].append(sc_node)
|
|
769
|
+
tmp_to_real_id_map[original_node_id].add(serial_id)
|
|
770
|
+
sc_node.id = serial_id
|
|
771
|
+
serial_id += 1
|
|
772
|
+
else:
|
|
773
|
+
serial_id, sub_tail_nodes, sc_node.sub_nodes = resolve_nodes(
|
|
774
|
+
sc_node.sub_nodes,
|
|
775
|
+
sc_node.root_inputs,
|
|
776
|
+
sc_node.convert_dict_inputs(),
|
|
777
|
+
serial_id,
|
|
778
|
+
sc_node.parents,
|
|
779
|
+
out_ds_name,
|
|
780
|
+
log_stream,
|
|
781
|
+
)
|
|
782
|
+
resolved_map[original_node_id] += sub_tail_nodes
|
|
783
|
+
tmp_to_real_id_map[original_node_id] |= set([n.id for n in sub_tail_nodes])
|
|
784
|
+
sc_node.id = serial_id
|
|
785
|
+
serial_id += 1
|
|
786
|
+
# convert parameters to parent IDs in conditions
|
|
787
|
+
if sc_node.condition:
|
|
788
|
+
convert_params_in_condition_to_parent_ids(sc_node.condition, sc_node.inputs, tmp_to_real_id_map)
|
|
789
|
+
# resolve outputs
|
|
790
|
+
if sc_node.is_leaf:
|
|
791
|
+
for tmp_name, tmp_data in sc_node.outputs.items():
|
|
792
|
+
tmp_data["value"] = f"{out_ds_name}_{sc_node.id:03d}_{sc_node.name}"
|
|
793
|
+
# add loop count for nodes in a loop
|
|
794
|
+
if sc_node.in_loop:
|
|
795
|
+
tmp_data["value"] += ".___idds___num_run___"
|
|
796
|
+
# return tails
|
|
797
|
+
tail_nodes = []
|
|
798
|
+
for node in all_nodes:
|
|
799
|
+
original_node_id = node_key_map.get(id(node), node.id)
|
|
800
|
+
if node.is_tail:
|
|
801
|
+
tail_nodes.append(node)
|
|
802
|
+
else:
|
|
803
|
+
tail_nodes += resolved_map[original_node_id]
|
|
804
|
+
return serial_id, tail_nodes, all_nodes
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
# condition item
|
|
808
|
+
class ConditionItem(object):
|
|
809
|
+
def __init__(self, left, right=None, operator=None):
|
|
810
|
+
if operator not in ["and", "or", "not", None]:
|
|
811
|
+
raise TypeError(f"unknown operator '{operator}'")
|
|
812
|
+
if operator in ["not", None] and right:
|
|
813
|
+
raise TypeError(f"right param is given for operator '{operator}'")
|
|
814
|
+
self.left = left
|
|
815
|
+
self.right = right
|
|
816
|
+
self.operator = operator
|
|
817
|
+
|
|
818
|
+
def get_dict_form(self, serial_id=None, dict_form=None):
|
|
819
|
+
if dict_form is None:
|
|
820
|
+
dict_form = {}
|
|
821
|
+
is_entry = True
|
|
822
|
+
else:
|
|
823
|
+
is_entry = False
|
|
824
|
+
if serial_id is None:
|
|
825
|
+
serial_id = 0
|
|
826
|
+
if isinstance(self.left, ConditionItem):
|
|
827
|
+
serial_id, dict_form = self.left.get_dict_form(serial_id, dict_form)
|
|
828
|
+
left_id = serial_id
|
|
829
|
+
serial_id += 1
|
|
830
|
+
else:
|
|
831
|
+
left_id = self.left
|
|
832
|
+
if isinstance(self.right, ConditionItem):
|
|
833
|
+
serial_id, dict_form = self.right.get_dict_form(serial_id, dict_form)
|
|
834
|
+
right_id = serial_id
|
|
835
|
+
serial_id += 1
|
|
836
|
+
else:
|
|
837
|
+
right_id = self.right
|
|
838
|
+
dict_form[serial_id] = {
|
|
839
|
+
"left": left_id,
|
|
840
|
+
"right": right_id,
|
|
841
|
+
"operator": self.operator,
|
|
842
|
+
}
|
|
843
|
+
if is_entry:
|
|
844
|
+
# sort
|
|
845
|
+
keys = sorted(dict_form.keys())
|
|
846
|
+
return [(k, dict_form[k]) for k in keys]
|
|
847
|
+
else:
|
|
848
|
+
return serial_id, dict_form
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
# convert nodes to workflow
|
|
852
|
+
def convert_nodes_to_workflow(nodes, workflow_node=None, workflow=None, workflow_name=None):
|
|
853
|
+
if workflow is None:
|
|
854
|
+
is_top = True
|
|
855
|
+
workflow = Workflow()
|
|
856
|
+
workflow.name = workflow_name
|
|
857
|
+
else:
|
|
858
|
+
is_top = False
|
|
859
|
+
id_work_map = {}
|
|
860
|
+
all_sub_id_work_map = {}
|
|
861
|
+
sub_to_id_map = {}
|
|
862
|
+
cond_dump_str = " Conditions\n"
|
|
863
|
+
class_dump_str = f"===== Workflow ID:{workflow_node.id if workflow_node else workflow_name} ====\n"
|
|
864
|
+
class_dump_str += " Works\n"
|
|
865
|
+
dump_str_list = []
|
|
866
|
+
# create works or workflows
|
|
867
|
+
for node in nodes:
|
|
868
|
+
if node.is_leaf:
|
|
869
|
+
# work
|
|
870
|
+
if node.type == "junction":
|
|
871
|
+
work = ATLASLocalPandaWork(task_parameters=node.task_params)
|
|
872
|
+
work.add_custom_condition("to_exit", True)
|
|
873
|
+
else:
|
|
874
|
+
work = ATLASPandaWork(task_parameters=node.task_params)
|
|
875
|
+
workflow.add_work(work)
|
|
876
|
+
id_work_map[node.id] = work
|
|
877
|
+
class_dump_str += f" {node.short_desc()} Class:{work.__class__.__name__}\n"
|
|
878
|
+
else:
|
|
879
|
+
# sub workflow
|
|
880
|
+
sub_workflow = Workflow()
|
|
881
|
+
id_work_map[node.id] = sub_workflow
|
|
882
|
+
class_dump_str += f" {node.short_desc()} Class:{sub_workflow.__class__.__name__}\n"
|
|
883
|
+
sub_id_work_map, tmp_dump_str_list = convert_nodes_to_workflow(node.sub_nodes, node, sub_workflow)
|
|
884
|
+
dump_str_list += tmp_dump_str_list
|
|
885
|
+
for sub_id in node.get_all_sub_node_ids():
|
|
886
|
+
all_sub_id_work_map[sub_id] = sub_workflow
|
|
887
|
+
sub_to_id_map[sub_id] = node.id
|
|
888
|
+
# add loop condition
|
|
889
|
+
if node.loop:
|
|
890
|
+
for sub_node in node.sub_nodes:
|
|
891
|
+
if sub_node.type == "junction":
|
|
892
|
+
# use to_continue for loop termination
|
|
893
|
+
j_work = sub_id_work_map[sub_node.id]
|
|
894
|
+
j_work.add_custom_condition(key="to_continue", value=True)
|
|
895
|
+
cond = Condition(cond=j_work.get_custom_condition_status)
|
|
896
|
+
sub_workflow.add_loop_condition(cond)
|
|
897
|
+
cond_dump_str += f" Loop in ID:{node.id} with terminator ID:{sub_node.id}\n"
|
|
898
|
+
break
|
|
899
|
+
workflow.add_work(sub_workflow)
|
|
900
|
+
# add conditions
|
|
901
|
+
for node in nodes:
|
|
902
|
+
if node.parents:
|
|
903
|
+
c_work = id_work_map[node.id]
|
|
904
|
+
if not node.condition:
|
|
905
|
+
# default conditions if unspecified
|
|
906
|
+
cond_func_list = []
|
|
907
|
+
for p_id in node.parents:
|
|
908
|
+
if p_id in id_work_map:
|
|
909
|
+
p_work = id_work_map[p_id]
|
|
910
|
+
str_p_id = p_id
|
|
911
|
+
elif p_id in all_sub_id_work_map:
|
|
912
|
+
p_work = all_sub_id_work_map[p_id]
|
|
913
|
+
str_p_id = sub_to_id_map[p_id]
|
|
914
|
+
else:
|
|
915
|
+
# head node
|
|
916
|
+
continue
|
|
917
|
+
if len(node.parents) > 1 or isinstance(p_work, Workflow) or node.type in ["junction", "reana", "gitlab"]:
|
|
918
|
+
cond_function = p_work.is_processed
|
|
919
|
+
else:
|
|
920
|
+
cond_function = p_work.is_started
|
|
921
|
+
if cond_function not in cond_func_list:
|
|
922
|
+
cond_func_list.append(cond_function)
|
|
923
|
+
cond_dump_str += f" Default Link ID:{str_p_id} {cond_function.__name__} -> ID:{node.id}\n"
|
|
924
|
+
cond = AndCondition(true_works=[c_work], conditions=cond_func_list)
|
|
925
|
+
workflow.add_condition(cond)
|
|
926
|
+
else:
|
|
927
|
+
# convert conditions
|
|
928
|
+
cond_list = node.condition.get_dict_form()
|
|
929
|
+
base_cond_map = {}
|
|
930
|
+
str_cond_map = {}
|
|
931
|
+
root_condition = None
|
|
932
|
+
for tmp_idx, base_cond in cond_list:
|
|
933
|
+
# leaf condition
|
|
934
|
+
if base_cond["right"] is None:
|
|
935
|
+
# condition based on works
|
|
936
|
+
cond_func_list = []
|
|
937
|
+
str_func_list = []
|
|
938
|
+
for p_id in base_cond["left"]:
|
|
939
|
+
if p_id in id_work_map:
|
|
940
|
+
p_work = id_work_map[p_id]
|
|
941
|
+
str_p_id = p_id
|
|
942
|
+
else:
|
|
943
|
+
p_work = all_sub_id_work_map[p_id]
|
|
944
|
+
str_p_id = sub_to_id_map[p_id]
|
|
945
|
+
# finished or failed
|
|
946
|
+
if base_cond["operator"] is None:
|
|
947
|
+
cond_function = p_work.is_processed
|
|
948
|
+
else:
|
|
949
|
+
cond_function = p_work.is_failed
|
|
950
|
+
cond_func_list.append(cond_function)
|
|
951
|
+
str_func_list.append(f"ID:{str_p_id} {cond_function.__name__}")
|
|
952
|
+
cond = AndCondition(conditions=cond_func_list)
|
|
953
|
+
base_cond_map[tmp_idx] = cond
|
|
954
|
+
str_func = "AND ".join(str_func_list)
|
|
955
|
+
str_cond_map[tmp_idx] = str_func
|
|
956
|
+
cond_dump_str += f" Unary Ops {cond.__class__.__name__}({str_func}) -> ID:{node.id}\n"
|
|
957
|
+
root_condition = cond
|
|
958
|
+
else:
|
|
959
|
+
# composite condition
|
|
960
|
+
l_str_func_list = []
|
|
961
|
+
r_str_func_list = []
|
|
962
|
+
if isinstance(base_cond["left"], set):
|
|
963
|
+
cond_func_list = []
|
|
964
|
+
for p_id in base_cond["left"]:
|
|
965
|
+
if p_id in id_work_map:
|
|
966
|
+
p_work = id_work_map[p_id]
|
|
967
|
+
str_p_id = p_id
|
|
968
|
+
else:
|
|
969
|
+
p_work = all_sub_id_work_map[p_id]
|
|
970
|
+
str_p_id = sub_to_id_map[p_id]
|
|
971
|
+
cond_function = p_work.is_processed
|
|
972
|
+
cond_func_list.append(cond_function)
|
|
973
|
+
l_str_func_list.append(f"ID:{str_p_id} {cond_function.__name__}")
|
|
974
|
+
l_cond = AndCondition(conditions=cond_func_list)
|
|
975
|
+
l_str_func = "AND ".join(l_str_func_list)
|
|
976
|
+
str_cond_map[base_cond["left"]] = l_str_func
|
|
977
|
+
else:
|
|
978
|
+
l_cond = base_cond_map[base_cond["left"]]
|
|
979
|
+
l_str_func = str_cond_map[base_cond["left"]]
|
|
980
|
+
if isinstance(base_cond["right"], set):
|
|
981
|
+
cond_func_list = []
|
|
982
|
+
for p_id in base_cond["right"]:
|
|
983
|
+
if p_id in id_work_map:
|
|
984
|
+
p_work = id_work_map[p_id]
|
|
985
|
+
str_p_id = p_id
|
|
986
|
+
else:
|
|
987
|
+
p_work = all_sub_id_work_map[p_id]
|
|
988
|
+
str_p_id = sub_to_id_map[p_id]
|
|
989
|
+
cond_function = p_work.is_processed
|
|
990
|
+
cond_func_list.append(cond_function)
|
|
991
|
+
r_str_func_list.append(f"ID:{str_p_id} {cond_function.__name__}")
|
|
992
|
+
r_cond = AndCondition(conditions=cond_func_list)
|
|
993
|
+
r_str_func = "AND ".join(r_str_func_list)
|
|
994
|
+
str_cond_map[base_cond["right"]] = r_str_func
|
|
995
|
+
else:
|
|
996
|
+
r_cond = base_cond_map[base_cond["right"]]
|
|
997
|
+
r_str_func = str_cond_map[base_cond["right"]]
|
|
998
|
+
if base_cond["operator"] == "and":
|
|
999
|
+
cond = AndCondition(
|
|
1000
|
+
conditions=[
|
|
1001
|
+
l_cond.is_condition_true,
|
|
1002
|
+
r_cond.is_condition_true,
|
|
1003
|
+
]
|
|
1004
|
+
)
|
|
1005
|
+
else:
|
|
1006
|
+
cond = OrCondition(
|
|
1007
|
+
conditions=[
|
|
1008
|
+
l_cond.is_condition_true,
|
|
1009
|
+
r_cond.is_condition_true,
|
|
1010
|
+
]
|
|
1011
|
+
)
|
|
1012
|
+
base_cond_map[tmp_idx] = cond
|
|
1013
|
+
cond_dump_str += f" Binary Ops {cond.__class__.__name__}({l_str_func}, {r_str_func}) for ID:{node.id}\n"
|
|
1014
|
+
root_condition = cond
|
|
1015
|
+
# set root condition
|
|
1016
|
+
if root_condition:
|
|
1017
|
+
root_condition.true_works = [c_work]
|
|
1018
|
+
workflow.add_condition(root_condition)
|
|
1019
|
+
# global parameters
|
|
1020
|
+
if workflow_node:
|
|
1021
|
+
tmp_global, tmp_workflow_global = workflow_node.get_global_parameters()
|
|
1022
|
+
if tmp_global:
|
|
1023
|
+
loop_locals = {}
|
|
1024
|
+
loop_slices = []
|
|
1025
|
+
for k, v in tmp_global.items():
|
|
1026
|
+
if not isinstance(v, dict):
|
|
1027
|
+
# normal looping locals
|
|
1028
|
+
loop_locals["user_" + k] = tmp_global[k]
|
|
1029
|
+
else:
|
|
1030
|
+
# sliced locals
|
|
1031
|
+
v["src"] = "user_" + v["src"]
|
|
1032
|
+
loop_slices.append([k, v])
|
|
1033
|
+
if loop_locals:
|
|
1034
|
+
workflow.set_global_parameters(loop_locals)
|
|
1035
|
+
for k, v in loop_slices:
|
|
1036
|
+
workflow.set_sliced_global_parameters(source=v["src"], index=v["idx"], name="user_" + k)
|
|
1037
|
+
cond_dump_str += "\n Looping local variables\n"
|
|
1038
|
+
cond_dump_str += f" {tmp_global}\n"
|
|
1039
|
+
if tmp_workflow_global:
|
|
1040
|
+
cond_dump_str += "\n Workflow local variable\n"
|
|
1041
|
+
cond_dump_str += f" {tmp_workflow_global}\n"
|
|
1042
|
+
# dump strings
|
|
1043
|
+
dump_str_list.insert(0, class_dump_str + "\n" + cond_dump_str + "\n\n")
|
|
1044
|
+
# return
|
|
1045
|
+
if not is_top:
|
|
1046
|
+
return id_work_map, dump_str_list
|
|
1047
|
+
return workflow, dump_str_list
|