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,178 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from pandaserver.dataservice import DataServiceUtils
|
|
4
|
+
from pandaserver.taskbuffer import EventServiceUtils
|
|
5
|
+
|
|
6
|
+
from . import AtlasPostProcessorUtils
|
|
7
|
+
from .PostProcessorBase import PostProcessorBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# post processor for ATLAS production
|
|
11
|
+
class AtlasProdPostProcessor(PostProcessorBase):
|
|
12
|
+
# constructor
|
|
13
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
14
|
+
PostProcessorBase.__init__(self, taskBufferIF, ddmIF)
|
|
15
|
+
|
|
16
|
+
# main
|
|
17
|
+
def doPostProcess(self, taskSpec, tmpLog):
|
|
18
|
+
# pre-check
|
|
19
|
+
try:
|
|
20
|
+
tmpStat = self.doPreCheck(taskSpec, tmpLog)
|
|
21
|
+
if tmpStat:
|
|
22
|
+
return self.SC_SUCCEEDED
|
|
23
|
+
except Exception:
|
|
24
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
25
|
+
tmpLog.error(f"doPreCheck failed with {errtype.__name__}:{errvalue}")
|
|
26
|
+
return self.SC_FATAL
|
|
27
|
+
# get DDM I/F
|
|
28
|
+
ddmIF = self.ddmIF.getInterface(taskSpec.vo)
|
|
29
|
+
# loop over all datasets
|
|
30
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
31
|
+
# skip pseudo output datasets
|
|
32
|
+
if datasetSpec.type in ["output"] and datasetSpec.isPseudo():
|
|
33
|
+
continue
|
|
34
|
+
try:
|
|
35
|
+
# remove wrong files
|
|
36
|
+
if datasetSpec.type in ["output"]:
|
|
37
|
+
# get successful files
|
|
38
|
+
okFiles = self.taskBufferIF.getSuccessfulFiles_JEDI(datasetSpec.jediTaskID, datasetSpec.datasetID)
|
|
39
|
+
if okFiles is None:
|
|
40
|
+
tmpLog.warning(f"failed to get successful files for {datasetSpec.datasetName}")
|
|
41
|
+
return self.SC_FAILED
|
|
42
|
+
# get files in dataset
|
|
43
|
+
ddmFiles = ddmIF.getFilesInDataset(datasetSpec.datasetName, skipDuplicate=False, ignoreUnknown=True)
|
|
44
|
+
tmpLog.debug(
|
|
45
|
+
f"datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName} has {len(okFiles)} files in DB, {len(ddmFiles)} files in DDM"
|
|
46
|
+
)
|
|
47
|
+
# check all files
|
|
48
|
+
toDelete = []
|
|
49
|
+
for tmpGUID, attMap in ddmFiles.items():
|
|
50
|
+
if attMap["lfn"] not in okFiles:
|
|
51
|
+
did = {"scope": attMap["scope"], "name": attMap["lfn"]}
|
|
52
|
+
toDelete.append(did)
|
|
53
|
+
tmpLog.debug(f"delete {attMap['lfn']} from {datasetSpec.datasetName}")
|
|
54
|
+
# delete
|
|
55
|
+
if toDelete != []:
|
|
56
|
+
ddmIF.deleteFilesFromDataset(datasetSpec.datasetName, toDelete)
|
|
57
|
+
except Exception:
|
|
58
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
59
|
+
tmpLog.warning(f"failed to remove wrong files with {errtype.__name__}:{errvalue}")
|
|
60
|
+
return self.SC_FAILED
|
|
61
|
+
try:
|
|
62
|
+
# freeze output and log datasets
|
|
63
|
+
if datasetSpec.type in ["output", "log", "trn_log"]:
|
|
64
|
+
tmpLog.info(f"freezing datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
65
|
+
ddmIF.freezeDataset(datasetSpec.datasetName, ignoreUnknown=True)
|
|
66
|
+
except Exception:
|
|
67
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
68
|
+
tmpLog.warning(f"failed to freeze datasets with {errtype.__name__}:{errvalue}")
|
|
69
|
+
return self.SC_FAILED
|
|
70
|
+
try:
|
|
71
|
+
# delete transient datasets
|
|
72
|
+
if datasetSpec.type in ["trn_output"]:
|
|
73
|
+
tmpLog.debug(f"deleting datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
74
|
+
retStr = ddmIF.deleteDataset(datasetSpec.datasetName, False, ignoreUnknown=True)
|
|
75
|
+
tmpLog.info(retStr)
|
|
76
|
+
except Exception:
|
|
77
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
78
|
+
tmpLog.warning(f"failed to delete datasets with {errtype.__name__}:{errvalue}")
|
|
79
|
+
# check duplication
|
|
80
|
+
if self.getFinalTaskStatus(taskSpec) in ["finished", "done"] and taskSpec.gshare != "Test":
|
|
81
|
+
nDup = self.taskBufferIF.checkDuplication_JEDI(taskSpec.jediTaskID)
|
|
82
|
+
tmpLog.debug(f"checked duplication with {nDup}")
|
|
83
|
+
if nDup is not None and nDup > 0:
|
|
84
|
+
errStr = f"paused since {nDup} duplication found"
|
|
85
|
+
taskSpec.oldStatus = self.getFinalTaskStatus(taskSpec)
|
|
86
|
+
taskSpec.status = "paused"
|
|
87
|
+
taskSpec.setErrDiag(errStr)
|
|
88
|
+
tmpLog.debug(errStr)
|
|
89
|
+
# delete ES datasets
|
|
90
|
+
if taskSpec.registerEsFiles():
|
|
91
|
+
try:
|
|
92
|
+
targetName = EventServiceUtils.getEsDatasetName(taskSpec.jediTaskID)
|
|
93
|
+
tmpLog.debug(f"deleting ES dataset name={targetName}")
|
|
94
|
+
retStr = ddmIF.deleteDataset(targetName, False, ignoreUnknown=True)
|
|
95
|
+
tmpLog.debug(retStr)
|
|
96
|
+
except Exception:
|
|
97
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
98
|
+
tmpLog.warning(f"failed to delete ES dataset with {errtype.__name__}:{errvalue}")
|
|
99
|
+
try:
|
|
100
|
+
AtlasPostProcessorUtils.send_notification(self.taskBufferIF, ddmIF, taskSpec, tmpLog)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
tmpLog.error(f"failed to talk to external system with {str(e)}")
|
|
103
|
+
return self.SC_FAILED
|
|
104
|
+
try:
|
|
105
|
+
self.doBasicPostProcess(taskSpec, tmpLog)
|
|
106
|
+
except Exception:
|
|
107
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
108
|
+
tmpLog.error(f"doBasicPostProcess failed with {errtype.__name__}:{errvalue}")
|
|
109
|
+
return self.SC_FATAL
|
|
110
|
+
return self.SC_SUCCEEDED
|
|
111
|
+
|
|
112
|
+
# final procedure
|
|
113
|
+
def doFinalProcedure(self, taskSpec, tmpLog):
|
|
114
|
+
tmpLog.info(f"final procedure for status={taskSpec.status} processingType={taskSpec.processingType}")
|
|
115
|
+
if taskSpec.status in ["done", "finished"] or (taskSpec.status == "paused" and taskSpec.oldStatus in ["done", "finished"]):
|
|
116
|
+
trnLifeTime = 14 * 24 * 60 * 60
|
|
117
|
+
trnLifeTimeMerge = 40 * 24 * 60 * 60
|
|
118
|
+
ddmIF = self.ddmIF.getInterface(taskSpec.vo)
|
|
119
|
+
# set lifetime to transient datasets
|
|
120
|
+
metaData = {"lifetime": trnLifeTime}
|
|
121
|
+
datasetTypeListI = set()
|
|
122
|
+
datasetTypeListO = set()
|
|
123
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
124
|
+
if datasetSpec.type in ["log", "output"]:
|
|
125
|
+
if datasetSpec.getTransient() is True:
|
|
126
|
+
tmpLog.debug(f"set metadata={str(metaData)} to datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
127
|
+
for metadataName, metadaValue in metaData.items():
|
|
128
|
+
ddmIF.setDatasetMetadata(datasetSpec.datasetName, metadataName, metadaValue)
|
|
129
|
+
# collect dataset types
|
|
130
|
+
datasetType = DataServiceUtils.getDatasetType(datasetSpec.datasetName)
|
|
131
|
+
if datasetType not in ["", None]:
|
|
132
|
+
if datasetSpec.type == "input":
|
|
133
|
+
datasetTypeListI.add(datasetType)
|
|
134
|
+
elif datasetSpec.type == "output":
|
|
135
|
+
datasetTypeListO.add(datasetType)
|
|
136
|
+
# set lifetime to parent transient datasets
|
|
137
|
+
if taskSpec.processingType in ["merge"] and (
|
|
138
|
+
taskSpec.status == "done"
|
|
139
|
+
or (taskSpec.status == "finished" and self.getFinalTaskStatus(taskSpec, checkParent=False) == "done")
|
|
140
|
+
or (
|
|
141
|
+
taskSpec.status == "paused"
|
|
142
|
+
and (taskSpec.oldStatus == "done" or (taskSpec.oldStatus == "finished" and self.getFinalTaskStatus(taskSpec, checkParent=False) == "done"))
|
|
143
|
+
)
|
|
144
|
+
):
|
|
145
|
+
# get parent task
|
|
146
|
+
if taskSpec.parent_tid not in [None, taskSpec.jediTaskID]:
|
|
147
|
+
# get parent
|
|
148
|
+
tmpStat, parentTaskSpec = self.taskBufferIF.getTaskDatasetsWithID_JEDI(taskSpec.parent_tid, None, False)
|
|
149
|
+
if tmpStat and parentTaskSpec is not None:
|
|
150
|
+
# set lifetime to parent datasets if they are transient
|
|
151
|
+
for datasetSpec in parentTaskSpec.datasetSpecList:
|
|
152
|
+
if datasetSpec.type in ["output"]:
|
|
153
|
+
# check dataset type
|
|
154
|
+
datasetType = DataServiceUtils.getDatasetType(datasetSpec.datasetName)
|
|
155
|
+
if datasetType not in datasetTypeListI or datasetType not in datasetTypeListO:
|
|
156
|
+
continue
|
|
157
|
+
metaData = {"lifetime": trnLifeTimeMerge}
|
|
158
|
+
tmpMetadata = ddmIF.getDatasetMetaData(datasetSpec.datasetName)
|
|
159
|
+
if tmpMetadata["transient"] is True:
|
|
160
|
+
tmpLog.debug(
|
|
161
|
+
"set metadata={0} to parent jediTaskID={1}:datasetID={2}:Name={3}".format(
|
|
162
|
+
str(metaData), taskSpec.parent_tid, datasetSpec.datasetID, datasetSpec.datasetName
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
for metadataName, metadaValue in metaData.items():
|
|
166
|
+
ddmIF.setDatasetMetadata(datasetSpec.datasetName, metadataName, metadaValue)
|
|
167
|
+
# set lifetime to failed datasets
|
|
168
|
+
if taskSpec.status in ["failed", "broken", "aborted"]:
|
|
169
|
+
trnLifeTime = 30 * 24 * 60 * 60
|
|
170
|
+
ddmIF = self.ddmIF.getInterface(taskSpec.vo)
|
|
171
|
+
# only log datasets
|
|
172
|
+
metaData = {"lifetime": trnLifeTime}
|
|
173
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
174
|
+
if datasetSpec.type in ["log"]:
|
|
175
|
+
tmpLog.debug(f"set metadata={str(metaData)} to failed datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
176
|
+
for metadataName, metadaValue in metaData.items():
|
|
177
|
+
ddmIF.setDatasetMetadata(datasetSpec.datasetName, metadataName, metadaValue)
|
|
178
|
+
return self.SC_SUCCEEDED
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .PostProcessorBase import PostProcessorBase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# post processor for general purpose
|
|
5
|
+
class GenPostProcessor(PostProcessorBase):
|
|
6
|
+
# constructor
|
|
7
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
8
|
+
PostProcessorBase.__init__(self, taskBufferIF, ddmIF)
|
|
9
|
+
self.failOnZeroOkFile = True
|
|
10
|
+
|
|
11
|
+
# main
|
|
12
|
+
def doPostProcess(self, taskSpec, tmpLog):
|
|
13
|
+
try:
|
|
14
|
+
# get DDM I/F
|
|
15
|
+
ddmIF = self.ddmIF.getInterface(taskSpec.vo, taskSpec.cloud)
|
|
16
|
+
# skip if DDM I/F is inactive
|
|
17
|
+
if not ddmIF:
|
|
18
|
+
tmpLog.info("skip due to inactive DDM I/F")
|
|
19
|
+
else:
|
|
20
|
+
# loop over all datasets
|
|
21
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
22
|
+
# only output and log datasets
|
|
23
|
+
if datasetSpec.type not in ["log", "output"]:
|
|
24
|
+
continue
|
|
25
|
+
tmpLog.info(f"freezing datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
26
|
+
ddmIF.freezeDataset(datasetSpec.datasetName, ignoreUnknown=True)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
tmpLog.warning(f"failed to freeze datasets with {str(e)}")
|
|
29
|
+
return self.SC_FAILED
|
|
30
|
+
try:
|
|
31
|
+
self.doBasicPostProcess(taskSpec, tmpLog)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
tmpLog.error(f"doBasicPostProcess failed with {str(e)}")
|
|
34
|
+
return self.SC_FATAL
|
|
35
|
+
return self.SC_SUCCEEDED
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
jedi_task_plain = """
|
|
2
|
+
Subject: JEDI notification for TaskID:{jedi_task_id} ({n_succeeded}/{n_total} {msg_succeeded})
|
|
3
|
+
|
|
4
|
+
Summary of TaskID:{jedi_task_id}
|
|
5
|
+
|
|
6
|
+
Created : {creation_time} (UTC)
|
|
7
|
+
Ended : {end_time} (UTC)
|
|
8
|
+
|
|
9
|
+
Final Status : {task_status}
|
|
10
|
+
|
|
11
|
+
Total Number of {input_str} : {n_total}
|
|
12
|
+
Succeeded : {n_succeeded}
|
|
13
|
+
Failed : {n_failed}
|
|
14
|
+
{cancelled_str} : {n_cancelled}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Error Dialog : {error_dialog}
|
|
18
|
+
|
|
19
|
+
{dataset_summary}
|
|
20
|
+
|
|
21
|
+
Parameters : {command}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
PandaMonURL : https://bigpanda.cern.ch/task/{jedi_task_id}/
|
|
25
|
+
|
|
26
|
+
Please use the smiley interface on this link to provide feedback on the task execution.
|
|
27
|
+
|
|
28
|
+
Estimated carbon footprint for the task
|
|
29
|
+
Succeeded : {carbon_succeeded}
|
|
30
|
+
Failed : {carbon_failed}
|
|
31
|
+
Cancelled : {carbon_cancelled}
|
|
32
|
+
_________________
|
|
33
|
+
Total : {carbon_total}
|
|
34
|
+
(More details on estimation: https://panda-wms.readthedocs.io/en/latest/advanced/carbon_footprint.html)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Report Panda problems of any sort to
|
|
38
|
+
|
|
39
|
+
the eGroup for help request
|
|
40
|
+
hn-atlas-dist-analysis-help@cern.ch
|
|
41
|
+
|
|
42
|
+
the Discourse forum for distributed computing help
|
|
43
|
+
https://atlas-talk.web.cern.ch/c/distributed-computing-help
|
|
44
|
+
|
|
45
|
+
the JIRA portal for software bug
|
|
46
|
+
https://its.cern.ch/jira/browse/ATLASPANDA
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
html_head = """
|
|
50
|
+
<!DOCTYPE html>
|
|
51
|
+
<html>
|
|
52
|
+
<head>
|
|
53
|
+
<style>
|
|
54
|
+
body {{
|
|
55
|
+
font-family: Arial, sans-serif;
|
|
56
|
+
}}
|
|
57
|
+
table {{
|
|
58
|
+
width: 100%;
|
|
59
|
+
border-collapse: collapse;
|
|
60
|
+
margin-top: 10px;
|
|
61
|
+
}}
|
|
62
|
+
th, td {{
|
|
63
|
+
border: 1px solid #dddddd;
|
|
64
|
+
text-align: left;
|
|
65
|
+
padding: 8px;
|
|
66
|
+
}}
|
|
67
|
+
th {{
|
|
68
|
+
background-color: #f2f2f2;
|
|
69
|
+
}}
|
|
70
|
+
.logo {{
|
|
71
|
+
width: 100px; /* Adjust the size as needed */
|
|
72
|
+
}}
|
|
73
|
+
</style>
|
|
74
|
+
<title> {title} </title>
|
|
75
|
+
</head>
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
jedi_task_html_body = """
|
|
79
|
+
<body>
|
|
80
|
+
<img class="logo" src="https://panda-wms.readthedocs.io/en/latest/_static/PanDA-rev-logo-small-200px.jpg" alt="PanDA Logo">
|
|
81
|
+
<h2>Summary of TaskID: <a href="https://bigpanda.cern.ch/task/{jedi_task_id}">{jedi_task_id}</a> </h2>
|
|
82
|
+
<p> 💬 Please use the smiley interface on this link to provide feedback on the task execution.</p>
|
|
83
|
+
<table>
|
|
84
|
+
<tr>
|
|
85
|
+
<th>Detail</th>
|
|
86
|
+
<th>Value</th>
|
|
87
|
+
</tr>
|
|
88
|
+
<tr>
|
|
89
|
+
<td>Created</td>
|
|
90
|
+
<td>{creation_time}</td>
|
|
91
|
+
</tr>
|
|
92
|
+
<tr>
|
|
93
|
+
<td>Ended</td>
|
|
94
|
+
<td>{end_time}</td>
|
|
95
|
+
</tr>
|
|
96
|
+
<tr>
|
|
97
|
+
<td>Final Status</td>
|
|
98
|
+
<td>{task_status}</td>
|
|
99
|
+
</tr>
|
|
100
|
+
</table>
|
|
101
|
+
|
|
102
|
+
<h3>Total Number of {input_str}</h3>
|
|
103
|
+
<table>
|
|
104
|
+
<tr>
|
|
105
|
+
<th>Category</th>
|
|
106
|
+
<th>Count</th>
|
|
107
|
+
</tr>
|
|
108
|
+
<tr>
|
|
109
|
+
<td>Succeeded</td>
|
|
110
|
+
<td>{n_succeeded}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td>Failed</td>
|
|
114
|
+
<td>{n_failed}</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td>{cancelled_str}</td>
|
|
118
|
+
<td>{n_cancelled}</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td>Total</td>
|
|
122
|
+
<td>{n_total}</td>
|
|
123
|
+
</tr>
|
|
124
|
+
</table>
|
|
125
|
+
|
|
126
|
+
<p><strong>Error Dialog:</strong> {error_dialog}</p>
|
|
127
|
+
|
|
128
|
+
<h3>Datasets</h3>
|
|
129
|
+
<table>
|
|
130
|
+
<tr>
|
|
131
|
+
<td><strong>In</strong></td>
|
|
132
|
+
<td>{datasets_in}</td>
|
|
133
|
+
</tr>
|
|
134
|
+
<tr>
|
|
135
|
+
<td><strong>Out</strong></td>
|
|
136
|
+
<td>{datasets_out}</td>
|
|
137
|
+
</tr>
|
|
138
|
+
<tr>
|
|
139
|
+
<td><strong>Log</strong></td>
|
|
140
|
+
<td>{datasets_log}</td>
|
|
141
|
+
</tr>
|
|
142
|
+
</table>
|
|
143
|
+
|
|
144
|
+
<h3>Parameters</h3>
|
|
145
|
+
<table>
|
|
146
|
+
<tr>
|
|
147
|
+
<td><strong>Command</strong></td>
|
|
148
|
+
<td>{command}</td>
|
|
149
|
+
</tr>
|
|
150
|
+
</table>
|
|
151
|
+
|
|
152
|
+
<h3>Estimated Carbon Footprint for the Task</h3>
|
|
153
|
+
<table>
|
|
154
|
+
<tr>
|
|
155
|
+
<th>Category</th>
|
|
156
|
+
<th>gCO2</th>
|
|
157
|
+
</tr>
|
|
158
|
+
<tr>
|
|
159
|
+
<td>Succeeded</td>
|
|
160
|
+
<td>{carbon_succeeded}</td>
|
|
161
|
+
</tr>
|
|
162
|
+
<tr>
|
|
163
|
+
<td>Failed</td>
|
|
164
|
+
<td>{carbon_failed}</td>
|
|
165
|
+
</tr>
|
|
166
|
+
<tr>
|
|
167
|
+
<td>Cancelled</td>
|
|
168
|
+
<td>{carbon_cancelled}</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr>
|
|
171
|
+
<td><strong>Total</strong></td>
|
|
172
|
+
<td><strong>{carbon_total}</strong></td>
|
|
173
|
+
</tr>
|
|
174
|
+
</table>
|
|
175
|
+
<p>More details on estimation: <a href="https://panda-wms.readthedocs.io/en/latest/advanced/carbon_footprint.html">https://panda-wms.readthedocs.io/en/latest/advanced/carbon_footprint.html</a></p>
|
|
176
|
+
|
|
177
|
+
<h3>Panda Problem Reporting</h3>
|
|
178
|
+
<ul>
|
|
179
|
+
<li>The eGroup for help request: <a href="mailto:hn-atlas-dist-analysis-help@cern.ch">hn-atlas-dist-analysis-help@cern.ch</a></li>
|
|
180
|
+
<li>The Discourse forum for distributed computing help: <a href="https://atlas-talk.web.cern.ch/c/distributed-computing-help">https://atlas-talk.web.cern.ch/c/distributed-computing-help</a></li>
|
|
181
|
+
<li>The JIRA portal for software bug: <a href="https://its.cern.ch/jira/browse/ATLASPANDA">https://its.cern.ch/jira/browse/ATLASPANDA</a></li>
|
|
182
|
+
</ul>
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
|
185
|
+
"""
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import smtplib
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
7
|
+
|
|
8
|
+
from pandajedi.jedicore import Interaction
|
|
9
|
+
from pandaserver.config import panda_config
|
|
10
|
+
from pandaserver.taskbuffer import EventServiceUtils
|
|
11
|
+
|
|
12
|
+
# port for SMTP server
|
|
13
|
+
smtpPortList = [25, 587]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# wrapper to patch smtplib.stderr to send debug info to logger
|
|
17
|
+
class StderrLogger(object):
|
|
18
|
+
def __init__(self, tmpLog):
|
|
19
|
+
self.tmpLog = tmpLog
|
|
20
|
+
|
|
21
|
+
def write(self, message):
|
|
22
|
+
message = message.strip()
|
|
23
|
+
if message != "":
|
|
24
|
+
self.tmpLog.debug(message)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# wrapper of SMTP to redirect messages
|
|
28
|
+
class MySMTP(smtplib.SMTP):
|
|
29
|
+
def set_log(self, tmp_log):
|
|
30
|
+
self.tmpLog = tmp_log
|
|
31
|
+
try:
|
|
32
|
+
self.org_stderr = getattr(smtplib, "stderr")
|
|
33
|
+
setattr(smtplib, "stderr", tmp_log)
|
|
34
|
+
except Exception:
|
|
35
|
+
self.org_stderr = None
|
|
36
|
+
|
|
37
|
+
def _print_debug(self, *args):
|
|
38
|
+
self.tmpLog.write(" ".join(map(str, args)))
|
|
39
|
+
|
|
40
|
+
def reset_log(self):
|
|
41
|
+
if self.org_stderr is not None:
|
|
42
|
+
setattr(smtplib, "stderr", self.org_stderr)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# base class for post process
|
|
46
|
+
class PostProcessorBase(object):
|
|
47
|
+
# constructor
|
|
48
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
49
|
+
self.ddmIF = ddmIF
|
|
50
|
+
self.taskBufferIF = taskBufferIF
|
|
51
|
+
self.msgType = "postprocessor"
|
|
52
|
+
self.failOnZeroOkFile = False
|
|
53
|
+
self.refresh()
|
|
54
|
+
|
|
55
|
+
# refresh
|
|
56
|
+
def refresh(self):
|
|
57
|
+
self.siteMapper = self.taskBufferIF.get_site_mapper()
|
|
58
|
+
|
|
59
|
+
# basic post procedure
|
|
60
|
+
def doBasicPostProcess(self, taskSpec, tmpLog):
|
|
61
|
+
# update task status
|
|
62
|
+
taskSpec.lockedBy = None
|
|
63
|
+
taskSpec.status = self.getFinalTaskStatus(taskSpec)
|
|
64
|
+
if taskSpec.status == "failed":
|
|
65
|
+
# set dialog for preprocess
|
|
66
|
+
if taskSpec.usePrePro() and not taskSpec.checkPreProcessed():
|
|
67
|
+
taskSpec.setErrDiag("Preprocessing step failed", True)
|
|
68
|
+
tmpMsg = f"set task_status={taskSpec.status}"
|
|
69
|
+
tmpLog.info(tmpMsg)
|
|
70
|
+
tmpLog.sendMsg(f"set task_status={taskSpec.status}", self.msgType)
|
|
71
|
+
# update dataset
|
|
72
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
73
|
+
if taskSpec.status in ["failed", "broken", "aborted"]:
|
|
74
|
+
datasetSpec.status = "failed"
|
|
75
|
+
else:
|
|
76
|
+
# set dataset status
|
|
77
|
+
if datasetSpec.type in ["output", "log", "lib"]:
|
|
78
|
+
# normal output datasets
|
|
79
|
+
if datasetSpec.nFiles and datasetSpec.nFilesFinished and datasetSpec.nFiles > datasetSpec.nFilesFinished:
|
|
80
|
+
datasetSpec.status = "finished"
|
|
81
|
+
else:
|
|
82
|
+
datasetSpec.status = "done"
|
|
83
|
+
elif datasetSpec.type.startswith("trn_") or datasetSpec.type.startswith("tmpl_"):
|
|
84
|
+
# set done for template or transient datasets
|
|
85
|
+
datasetSpec.status = "done"
|
|
86
|
+
else:
|
|
87
|
+
# not for input
|
|
88
|
+
continue
|
|
89
|
+
# set nFiles
|
|
90
|
+
if datasetSpec.type in ["output", "log", "lib"]:
|
|
91
|
+
datasetSpec.nFiles = datasetSpec.nFilesFinished
|
|
92
|
+
self.taskBufferIF.updateDataset_JEDI(datasetSpec, {"datasetID": datasetSpec.datasetID, "jediTaskID": datasetSpec.jediTaskID})
|
|
93
|
+
# trigger internal dataset cleanup
|
|
94
|
+
self.taskBufferIF.trigger_cleanup_internal_datasets(taskSpec.jediTaskID)
|
|
95
|
+
# end time
|
|
96
|
+
taskSpec.endTime = naive_utcnow()
|
|
97
|
+
# update task
|
|
98
|
+
self.taskBufferIF.updateTask_JEDI(taskSpec, {"jediTaskID": taskSpec.jediTaskID}, updateDEFT=True)
|
|
99
|
+
# kill or kick child tasks
|
|
100
|
+
if taskSpec.status in ["failed", "broken", "aborted"]:
|
|
101
|
+
self.taskBufferIF.killChildTasks_JEDI(taskSpec.jediTaskID, taskSpec.status)
|
|
102
|
+
else:
|
|
103
|
+
self.taskBufferIF.kickChildTasks_JEDI(taskSpec.jediTaskID)
|
|
104
|
+
tmpLog.debug(f"doBasicPostProcess done with taskStatus={taskSpec.status}")
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# final procedure
|
|
108
|
+
def doFinalProcedure(self, taskSpec, tmpLog):
|
|
109
|
+
return self.SC_SUCCEEDED
|
|
110
|
+
|
|
111
|
+
# send mail
|
|
112
|
+
def sendMail(self, jediTaskID, fromAdd, toAdd, msgBody, nTry, fileBackUp, tmpLog):
|
|
113
|
+
tmpLog.debug(f"sending notification to {toAdd}\n{msgBody}")
|
|
114
|
+
for iTry in range(nTry):
|
|
115
|
+
try:
|
|
116
|
+
stderrLog = StderrLogger(tmpLog)
|
|
117
|
+
smtpPort = smtpPortList[iTry % len(smtpPortList)]
|
|
118
|
+
server = MySMTP(panda_config.emailSMTPsrv, smtpPort)
|
|
119
|
+
server.set_debuglevel(1)
|
|
120
|
+
server.set_log(stderrLog)
|
|
121
|
+
server.ehlo()
|
|
122
|
+
server.starttls()
|
|
123
|
+
out = server.sendmail(fromAdd, toAdd, msgBody)
|
|
124
|
+
tmpLog.debug(str(out))
|
|
125
|
+
server.quit()
|
|
126
|
+
break
|
|
127
|
+
except Exception as e:
|
|
128
|
+
if iTry + 1 < nTry:
|
|
129
|
+
# sleep for retry
|
|
130
|
+
tmpLog.debug(f"sleep {iTry} due to {str(e)}")
|
|
131
|
+
time.sleep(30)
|
|
132
|
+
else:
|
|
133
|
+
tmpLog.error(f"failed to send notification with {str(e)}")
|
|
134
|
+
if fileBackUp:
|
|
135
|
+
# write to file which is processed in add.py
|
|
136
|
+
mailFile = "{0}/jmail_{1}_{2}" % (panda_config.logdir, jediTaskID, uuid.uuid4())
|
|
137
|
+
oMail = open(mailFile, "w")
|
|
138
|
+
oMail.write(str(jediTaskID) + "\n" + toAdd + "\n" + msgBody)
|
|
139
|
+
oMail.close()
|
|
140
|
+
break
|
|
141
|
+
try:
|
|
142
|
+
server.reset_log()
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
# return email sender
|
|
147
|
+
def senderAddress(self):
|
|
148
|
+
return panda_config.emailSender
|
|
149
|
+
|
|
150
|
+
# get task completeness
|
|
151
|
+
def getTaskCompleteness(self, taskSpec):
|
|
152
|
+
nFiles = 0
|
|
153
|
+
nFilesFinished = 0
|
|
154
|
+
totalInputEvents = 0
|
|
155
|
+
totalOkEvents = 0
|
|
156
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
157
|
+
if datasetSpec.isMasterInput():
|
|
158
|
+
if datasetSpec.status == "removed":
|
|
159
|
+
continue
|
|
160
|
+
nFiles += datasetSpec.nFiles
|
|
161
|
+
nFilesFinished += datasetSpec.nFilesFinished
|
|
162
|
+
try:
|
|
163
|
+
totalInputEvents += datasetSpec.nEvents
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
try:
|
|
167
|
+
totalOkEvents += datasetSpec.nEventsUsed
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
# completeness
|
|
171
|
+
if totalInputEvents != 0:
|
|
172
|
+
taskCompleteness = float(totalOkEvents) / float(totalInputEvents) * 1000.0
|
|
173
|
+
elif nFiles != 0:
|
|
174
|
+
taskCompleteness = float(nFilesFinished) / float(nFiles) * 1000.0
|
|
175
|
+
else:
|
|
176
|
+
taskCompleteness = 0
|
|
177
|
+
return nFiles, nFilesFinished, totalInputEvents, totalOkEvents, taskCompleteness
|
|
178
|
+
|
|
179
|
+
# get final task status
|
|
180
|
+
def getFinalTaskStatus(self, taskSpec, checkParent=True, checkGoal=False):
|
|
181
|
+
# count nFiles and nEvents
|
|
182
|
+
nFiles, nFilesFinished, totalInputEvents, totalOkEvents, taskCompleteness = self.getTaskCompleteness(taskSpec)
|
|
183
|
+
# set new task status
|
|
184
|
+
if taskSpec.status == "tobroken":
|
|
185
|
+
status = "broken"
|
|
186
|
+
elif taskSpec.status == "toabort":
|
|
187
|
+
status = "aborted"
|
|
188
|
+
elif taskSpec.status == "paused":
|
|
189
|
+
status = "paused"
|
|
190
|
+
elif self.failOnZeroOkFile and nFiles == nFilesFinished == 0:
|
|
191
|
+
status = "failed"
|
|
192
|
+
elif nFiles == nFilesFinished:
|
|
193
|
+
# check parent status
|
|
194
|
+
if checkParent and taskSpec.parent_tid not in [None, taskSpec.jediTaskID]:
|
|
195
|
+
parent_status = self.taskBufferIF.getTaskStatus_JEDI(taskSpec.parent_tid)
|
|
196
|
+
if parent_status in ["failed", "broken", "aborted"]:
|
|
197
|
+
status = "failed"
|
|
198
|
+
elif parent_status != "done":
|
|
199
|
+
status = "finished"
|
|
200
|
+
else:
|
|
201
|
+
# check if input is mutable
|
|
202
|
+
inputMutable = False
|
|
203
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
204
|
+
if datasetSpec.isMasterInput() and datasetSpec.state == "mutable":
|
|
205
|
+
inputMutable = True
|
|
206
|
+
break
|
|
207
|
+
if inputMutable:
|
|
208
|
+
status = "finished"
|
|
209
|
+
else:
|
|
210
|
+
status = "done"
|
|
211
|
+
else:
|
|
212
|
+
status = "done"
|
|
213
|
+
elif nFilesFinished == 0:
|
|
214
|
+
status = "failed"
|
|
215
|
+
else:
|
|
216
|
+
status = "finished"
|
|
217
|
+
# task goal
|
|
218
|
+
if taskSpec.goal is None:
|
|
219
|
+
taskGoal = 1000
|
|
220
|
+
else:
|
|
221
|
+
taskGoal = taskSpec.goal
|
|
222
|
+
# fail if goal is not reached
|
|
223
|
+
if (
|
|
224
|
+
taskSpec.failGoalUnreached()
|
|
225
|
+
and status == "finished"
|
|
226
|
+
and (not taskSpec.useExhausted() or (taskSpec.useExhausted() and taskSpec.status in ["passed"]))
|
|
227
|
+
):
|
|
228
|
+
if taskCompleteness < taskGoal:
|
|
229
|
+
status = "failed"
|
|
230
|
+
# HPO tasks always go to finished
|
|
231
|
+
if taskSpec.is_hpo_workflow():
|
|
232
|
+
event_stat = self.taskBufferIF.get_event_statistics(taskSpec.jediTaskID)
|
|
233
|
+
if event_stat is not None and event_stat.get(EventServiceUtils.ST_finished):
|
|
234
|
+
status = "finished"
|
|
235
|
+
# check goal only
|
|
236
|
+
if checkGoal:
|
|
237
|
+
# no goal
|
|
238
|
+
if taskSpec.goal is not None and taskCompleteness >= taskGoal:
|
|
239
|
+
return True
|
|
240
|
+
return False
|
|
241
|
+
# return status
|
|
242
|
+
return status
|
|
243
|
+
|
|
244
|
+
# pre-check
|
|
245
|
+
def doPreCheck(self, taskSpec, tmpLog):
|
|
246
|
+
# send task to exhausted
|
|
247
|
+
if (
|
|
248
|
+
taskSpec.useExhausted()
|
|
249
|
+
and taskSpec.status not in ["passed"]
|
|
250
|
+
and self.getFinalTaskStatus(taskSpec) in ["finished"]
|
|
251
|
+
and not self.getFinalTaskStatus(taskSpec, checkGoal=True)
|
|
252
|
+
):
|
|
253
|
+
taskSpec.status = "exhausted"
|
|
254
|
+
if self.getFinalTaskStatus(taskSpec, checkParent=False) == "done":
|
|
255
|
+
taskSpec.errorDialog = "exhausted since the parent task was incomplete and didn't reach the goal"
|
|
256
|
+
else:
|
|
257
|
+
taskSpec.errorDialog = "exhausted since the task was incomplete and didn't reach the goal"
|
|
258
|
+
taskSpec.lockedBy = None
|
|
259
|
+
taskSpec.lockedTime = None
|
|
260
|
+
# update task
|
|
261
|
+
tmpLog.info(f"set task_status={taskSpec.status}")
|
|
262
|
+
self.taskBufferIF.updateTask_JEDI(taskSpec, {"jediTaskID": taskSpec.jediTaskID}, updateDEFT=True)
|
|
263
|
+
# kick child tasks
|
|
264
|
+
self.taskBufferIF.kickChildTasks_JEDI(taskSpec.jediTaskID)
|
|
265
|
+
return True
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
Interaction.installSC(PostProcessorBase)
|
|
File without changes
|