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,79 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import os
|
|
3
|
+
import socket
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
8
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
9
|
+
|
|
10
|
+
from pandajedi.jediconfig import jedi_config
|
|
11
|
+
from pandajedi.jedicore import Interaction
|
|
12
|
+
from pandajedi.jedicore.FactoryBase import FactoryBase
|
|
13
|
+
from pandajedi.jedicore.MsgWrapper import MsgWrapper
|
|
14
|
+
|
|
15
|
+
from .JediKnight import JediKnight
|
|
16
|
+
|
|
17
|
+
logger = PandaLogger().getLogger(__name__.split(".")[-1])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# worker class for watchdog
|
|
21
|
+
class WatchDog(JediKnight, FactoryBase):
|
|
22
|
+
# constructor
|
|
23
|
+
def __init__(self, commuChannel, taskBufferIF, ddmIF, vos, prodSourceLabels, subStr, period):
|
|
24
|
+
self.vos = self.parseInit(vos)
|
|
25
|
+
self.prodSourceLabels = self.parseInit(prodSourceLabels)
|
|
26
|
+
self.subStr = subStr
|
|
27
|
+
self.period = period
|
|
28
|
+
self.pid = f"{socket.getfqdn().split('.')[0]}-{os.getpid()}-dog"
|
|
29
|
+
JediKnight.__init__(self, commuChannel, taskBufferIF, ddmIF, logger)
|
|
30
|
+
FactoryBase.__init__(self, self.vos, self.prodSourceLabels, logger, jedi_config.watchdog.modConfig)
|
|
31
|
+
|
|
32
|
+
# main
|
|
33
|
+
def start(self):
|
|
34
|
+
# start base classes
|
|
35
|
+
JediKnight.start(self)
|
|
36
|
+
FactoryBase.initializeMods(self, self.taskBufferIF, self.ddmIF)
|
|
37
|
+
# go into main loop
|
|
38
|
+
while True:
|
|
39
|
+
startTime = naive_utcnow()
|
|
40
|
+
try:
|
|
41
|
+
# get logger
|
|
42
|
+
tmpLog = MsgWrapper(logger)
|
|
43
|
+
tmpLog.info("start")
|
|
44
|
+
# loop over all vos
|
|
45
|
+
for vo in self.vos:
|
|
46
|
+
# loop over all sourceLabels
|
|
47
|
+
for prodSourceLabel in self.prodSourceLabels:
|
|
48
|
+
# vo/prodSourceLabel specific action
|
|
49
|
+
impl = self.getImpl(vo, prodSourceLabel, subType=self.subStr)
|
|
50
|
+
if impl is not None:
|
|
51
|
+
plugin_name = impl.__class__.__name__
|
|
52
|
+
tmpLog.info(f"pre-action for vo={vo} label={prodSourceLabel} cls={plugin_name}")
|
|
53
|
+
impl.pre_action(tmpLog, vo, prodSourceLabel, self.pid)
|
|
54
|
+
tmpLog.info(f"do action for vo={vo} label={prodSourceLabel} cls={plugin_name}")
|
|
55
|
+
tmpStat = impl.doAction()
|
|
56
|
+
if tmpStat != Interaction.SC_SUCCEEDED:
|
|
57
|
+
tmpLog.error(f"failed to run special action for vo={vo} label={prodSourceLabel} cls={plugin_name}")
|
|
58
|
+
else:
|
|
59
|
+
tmpLog.info(f"done for vo={vo} label={prodSourceLabel} cls={plugin_name}")
|
|
60
|
+
tmpLog.info("done")
|
|
61
|
+
except Exception:
|
|
62
|
+
errtype, errvalue = sys.exc_info()[:2]
|
|
63
|
+
tmpLog.error(f"failed in {self.__class__.__name__}.start() with {errtype.__name__} {errvalue}")
|
|
64
|
+
# sleep if needed
|
|
65
|
+
loopCycle = jedi_config.watchdog.loopCycle if self.period is None else self.period
|
|
66
|
+
timeDelta = naive_utcnow() - startTime
|
|
67
|
+
sleepPeriod = loopCycle - timeDelta.seconds
|
|
68
|
+
if sleepPeriod > 0:
|
|
69
|
+
time.sleep(sleepPeriod)
|
|
70
|
+
# randomize cycle
|
|
71
|
+
self.randomSleep(max_val=loopCycle)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# launch
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def launcher(commuChannel, taskBufferIF, ddmIF, vos=None, prodSourceLabels=None, subStr=None, period=None):
|
|
78
|
+
p = WatchDog(commuChannel, taskBufferIF, ddmIF, vos, prodSourceLabels, subStr, period)
|
|
79
|
+
p.start()
|
|
File without changes
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import random
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import traceback
|
|
7
|
+
from email.mime.multipart import MIMEMultipart
|
|
8
|
+
from email.mime.text import MIMEText
|
|
9
|
+
|
|
10
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
11
|
+
|
|
12
|
+
from pandajedi.jedirefine import RefinerUtils
|
|
13
|
+
from pandaserver.taskbuffer import EventServiceUtils
|
|
14
|
+
|
|
15
|
+
from .MailTemplates import html_head, jedi_task_html_body, jedi_task_plain
|
|
16
|
+
from .PostProcessorBase import PostProcessorBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def format_weight(weight):
|
|
20
|
+
power = 1000
|
|
21
|
+
n = 0
|
|
22
|
+
power_labels = {0: "gCO2", 1: "kgCO2", 2: "tCO2", 3: "MtCO2", 4: "GtCO2"}
|
|
23
|
+
while weight > power:
|
|
24
|
+
weight /= power
|
|
25
|
+
n += 1
|
|
26
|
+
|
|
27
|
+
weight_str = f"{weight:.2f} {power_labels[n]}"
|
|
28
|
+
return weight_str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# post processor for ATLAS production
|
|
32
|
+
class AtlasAnalPostProcessor(PostProcessorBase):
|
|
33
|
+
# constructor
|
|
34
|
+
def __init__(self, taskBufferIF, ddmIF):
|
|
35
|
+
PostProcessorBase.__init__(self, taskBufferIF, ddmIF)
|
|
36
|
+
self.taskParamMap = None
|
|
37
|
+
self.user_container_lifetime = taskBufferIF.getConfigValue("user_output", "OUTPUT_CONTAINER_LIFETIME", "jedi")
|
|
38
|
+
if not self.user_container_lifetime:
|
|
39
|
+
self.user_container_lifetime = 14
|
|
40
|
+
self.user_container_lifetime *= 24 * 60 * 60
|
|
41
|
+
|
|
42
|
+
# main
|
|
43
|
+
def doPostProcess(self, taskSpec, tmp_logger):
|
|
44
|
+
# freeze datasets
|
|
45
|
+
try:
|
|
46
|
+
# get DDM I/F
|
|
47
|
+
ddmIF = self.ddmIF.getInterface(taskSpec.vo)
|
|
48
|
+
# shuffle datasets
|
|
49
|
+
random.shuffle(taskSpec.datasetSpecList)
|
|
50
|
+
# loop over all datasets
|
|
51
|
+
use_lib = False
|
|
52
|
+
n_ok_lib = 0
|
|
53
|
+
lock_update_time = naive_utcnow()
|
|
54
|
+
done_containers = set()
|
|
55
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
56
|
+
# ignore template
|
|
57
|
+
if datasetSpec.type.startswith("tmpl_"):
|
|
58
|
+
continue
|
|
59
|
+
# only output, log or lib datasets
|
|
60
|
+
if not datasetSpec.type.endswith("log") and not datasetSpec.type.endswith("output") and not datasetSpec.type == "lib":
|
|
61
|
+
continue
|
|
62
|
+
# only user group, or panda dataset
|
|
63
|
+
if (
|
|
64
|
+
not datasetSpec.datasetName.startswith("user")
|
|
65
|
+
and not datasetSpec.datasetName.startswith("panda")
|
|
66
|
+
and not datasetSpec.datasetName.startswith("group")
|
|
67
|
+
):
|
|
68
|
+
continue
|
|
69
|
+
# check if already closed
|
|
70
|
+
dataset_attrs = self.taskBufferIF.getDatasetAttributes_JEDI(datasetSpec.jediTaskID, datasetSpec.datasetID, ["state"])
|
|
71
|
+
if "state" in dataset_attrs and dataset_attrs["state"] == "closed":
|
|
72
|
+
tmp_logger.info(f"skip freezing closed datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
73
|
+
closed_flag = True
|
|
74
|
+
else:
|
|
75
|
+
closed_flag = False
|
|
76
|
+
# remove wrong files
|
|
77
|
+
if not closed_flag and datasetSpec.type in ["output"]:
|
|
78
|
+
# get successful files
|
|
79
|
+
ok_files = self.taskBufferIF.getSuccessfulFiles_JEDI(datasetSpec.jediTaskID, datasetSpec.datasetID)
|
|
80
|
+
if ok_files is None:
|
|
81
|
+
tmp_logger.warning(f"failed to get successful files for {datasetSpec.datasetName}")
|
|
82
|
+
return self.SC_FAILED
|
|
83
|
+
# get files in dataset
|
|
84
|
+
ddm_files = ddmIF.getFilesInDataset(datasetSpec.datasetName, skipDuplicate=False)
|
|
85
|
+
tmp_logger.debug(
|
|
86
|
+
f"datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName} has {len(ok_files)} files in DB, {len(ddm_files)} files in DDM"
|
|
87
|
+
)
|
|
88
|
+
# check all files
|
|
89
|
+
to_delete = []
|
|
90
|
+
for tmpGUID, attMap in ddm_files.items():
|
|
91
|
+
if attMap["lfn"] not in ok_files:
|
|
92
|
+
did = {"scope": attMap["scope"], "name": attMap["lfn"]}
|
|
93
|
+
to_delete.append(did)
|
|
94
|
+
tmp_logger.debug(f"delete {attMap['lfn']} from {datasetSpec.datasetName}")
|
|
95
|
+
# delete
|
|
96
|
+
if to_delete:
|
|
97
|
+
ddmIF.deleteFilesFromDataset(datasetSpec.datasetName, to_delete)
|
|
98
|
+
|
|
99
|
+
# freeze datasets
|
|
100
|
+
if not closed_flag and not (datasetSpec.type.startswith("trn_") and datasetSpec.type not in ["trn_log"]):
|
|
101
|
+
tmp_logger.debug(f"freeze datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
102
|
+
ddmIF.freezeDataset(datasetSpec.datasetName, ignoreUnknown=True)
|
|
103
|
+
else:
|
|
104
|
+
if datasetSpec.type.startswith("trn_") and datasetSpec.type not in ["trn_log"]:
|
|
105
|
+
tmp_logger.debug(f"skip freezing transient datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
106
|
+
# update dataset
|
|
107
|
+
datasetSpec.state = "closed"
|
|
108
|
+
datasetSpec.stateCheckTime = naive_utcnow()
|
|
109
|
+
|
|
110
|
+
# check if build step was succeeded
|
|
111
|
+
if datasetSpec.type == "lib":
|
|
112
|
+
use_lib = True
|
|
113
|
+
else:
|
|
114
|
+
n_ok_lib += 1
|
|
115
|
+
# delete transient or empty datasets
|
|
116
|
+
if not closed_flag:
|
|
117
|
+
empty_only = True
|
|
118
|
+
if datasetSpec.type.startswith("trn_") and datasetSpec.type not in ["trn_log"]:
|
|
119
|
+
empty_only = False
|
|
120
|
+
retStr = ddmIF.deleteDataset(datasetSpec.datasetName, empty_only, ignoreUnknown=True)
|
|
121
|
+
tmp_logger.debug(retStr)
|
|
122
|
+
# extend lifetime
|
|
123
|
+
if datasetSpec.type in ["output"] and datasetSpec.datasetName.startswith("user"):
|
|
124
|
+
tmp_logger.debug(f"extend lifetime datasetID={datasetSpec.datasetID}:Name={datasetSpec.datasetName}")
|
|
125
|
+
ddmIF.updateReplicationRules(
|
|
126
|
+
datasetSpec.datasetName, {"type=.+": {"lifetime": 14 * 24 * 60 * 60}, "(SCRATCH|USER)DISK": {"lifetime": 14 * 24 * 60 * 60}}
|
|
127
|
+
)
|
|
128
|
+
# extend container rules
|
|
129
|
+
if datasetSpec.containerName and datasetSpec.containerName not in done_containers:
|
|
130
|
+
tmp_logger.debug(f"extend lifetime container:Name={datasetSpec.containerName}")
|
|
131
|
+
ddmIF.updateReplicationRules(
|
|
132
|
+
datasetSpec.containerName,
|
|
133
|
+
{"type=.+": {"lifetime": self.user_container_lifetime}, "(SCRATCH|USER)DISK": {"lifetime": self.user_container_lifetime}},
|
|
134
|
+
)
|
|
135
|
+
done_containers.add(datasetSpec.containerName)
|
|
136
|
+
# update dataset in DB
|
|
137
|
+
self.taskBufferIF.updateDatasetAttributes_JEDI(
|
|
138
|
+
datasetSpec.jediTaskID, datasetSpec.datasetID, {"state": datasetSpec.state, "stateCheckTime": datasetSpec.stateCheckTime}
|
|
139
|
+
)
|
|
140
|
+
# update task lock
|
|
141
|
+
if naive_utcnow() - lock_update_time > datetime.timedelta(minutes=5):
|
|
142
|
+
lock_update_time = naive_utcnow()
|
|
143
|
+
# update lock
|
|
144
|
+
self.taskBufferIF.updateTaskLock_JEDI(taskSpec.jediTaskID)
|
|
145
|
+
# dialog
|
|
146
|
+
if use_lib and n_ok_lib == 0:
|
|
147
|
+
taskSpec.setErrDiag("No build jobs succeeded", True)
|
|
148
|
+
except Exception:
|
|
149
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
150
|
+
tmp_logger.warning(f"failed to freeze datasets with {err_type.__name__}:{err_value}")
|
|
151
|
+
ret_val = self.SC_SUCCEEDED
|
|
152
|
+
try:
|
|
153
|
+
self.doBasicPostProcess(taskSpec, tmp_logger)
|
|
154
|
+
except Exception:
|
|
155
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
156
|
+
tmp_logger.error(f"doBasicPostProcess failed with {err_type.__name__}:{err_value}")
|
|
157
|
+
ret_val = self.SC_FATAL
|
|
158
|
+
return ret_val
|
|
159
|
+
|
|
160
|
+
# final procedure
|
|
161
|
+
def doFinalProcedure(self, taskSpec, tmp_logger):
|
|
162
|
+
# check email address
|
|
163
|
+
to_add = self.getEmail(taskSpec.userName, taskSpec.vo, tmp_logger)
|
|
164
|
+
|
|
165
|
+
# calculate carbon footprint for the task
|
|
166
|
+
try:
|
|
167
|
+
carbon_footprint = self.taskBufferIF.get_task_carbon_footprint(taskSpec.jediTaskID, level="global")
|
|
168
|
+
carbon_footprint_redacted = {}
|
|
169
|
+
zero = "0 gCO2"
|
|
170
|
+
|
|
171
|
+
for job_status in ["finished", "failed", "cancelled", "total"]:
|
|
172
|
+
if carbon_footprint and job_status in carbon_footprint:
|
|
173
|
+
carbon_footprint_redacted[job_status] = format_weight(carbon_footprint[job_status])
|
|
174
|
+
else:
|
|
175
|
+
carbon_footprint_redacted[job_status] = zero
|
|
176
|
+
except Exception:
|
|
177
|
+
carbon_footprint_redacted = {}
|
|
178
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
179
|
+
tmp_logger.error(f"failed to calculate task carbon footprint {err_type.__name__}:{err_value}")
|
|
180
|
+
|
|
181
|
+
# read task parameters
|
|
182
|
+
try:
|
|
183
|
+
task_parameters = self.taskBufferIF.getTaskParamsWithID_JEDI(taskSpec.jediTaskID)
|
|
184
|
+
self.taskParamMap = RefinerUtils.decodeJSON(task_parameters)
|
|
185
|
+
except Exception:
|
|
186
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
187
|
+
tmp_logger.error(f"task param conversion from json failed with {err_type.__name__}:{err_value}")
|
|
188
|
+
if to_add is None or (self.taskParamMap is not None and "noEmail" in self.taskParamMap and self.taskParamMap["noEmail"] is True):
|
|
189
|
+
tmp_logger.debug("email notification is suppressed")
|
|
190
|
+
else:
|
|
191
|
+
try:
|
|
192
|
+
# send email notification
|
|
193
|
+
from_add = self.senderAddress()
|
|
194
|
+
html_text, plain_text, subject = self.compose_message(taskSpec, carbon_footprint_redacted)
|
|
195
|
+
msg = MIMEMultipart("alternative")
|
|
196
|
+
|
|
197
|
+
msg["Subject"] = subject
|
|
198
|
+
msg["From"] = from_add
|
|
199
|
+
msg["To"] = to_add
|
|
200
|
+
|
|
201
|
+
# Record the MIME types of both parts - text/plain and text/html.
|
|
202
|
+
part1 = MIMEText(plain_text, "plain")
|
|
203
|
+
part2 = MIMEText(html_text, "html")
|
|
204
|
+
|
|
205
|
+
# Attach parts into message container.
|
|
206
|
+
# According to RFC 2046, the last part of a multipart message, in this case
|
|
207
|
+
# the HTML message, is best and preferred.
|
|
208
|
+
msg.attach(part1)
|
|
209
|
+
msg.attach(part2)
|
|
210
|
+
self.sendMail(taskSpec.jediTaskID, from_add, to_add, msg.as_string(), 3, False, tmp_logger)
|
|
211
|
+
except Exception:
|
|
212
|
+
tmp_logger.error(traceback.format_exc())
|
|
213
|
+
return self.SC_SUCCEEDED
|
|
214
|
+
|
|
215
|
+
# compose mail message
|
|
216
|
+
def compose_message(self, taskSpec, carbon_footprint):
|
|
217
|
+
# summary
|
|
218
|
+
input_datasets = []
|
|
219
|
+
output_datasets = []
|
|
220
|
+
log_datasets = []
|
|
221
|
+
n_total_jobs = 0
|
|
222
|
+
n_succeeded_jobs = 0
|
|
223
|
+
n_failed_jobs = 0
|
|
224
|
+
n_cancelled_jobs = 0
|
|
225
|
+
|
|
226
|
+
if not taskSpec.is_hpo_workflow():
|
|
227
|
+
input_str = "Inputs"
|
|
228
|
+
cancelled_str = "Cancelled "
|
|
229
|
+
|
|
230
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
231
|
+
# dataset summary
|
|
232
|
+
if datasetSpec.type == "log":
|
|
233
|
+
if datasetSpec.containerName not in log_datasets:
|
|
234
|
+
log_datasets.append(datasetSpec.containerName)
|
|
235
|
+
elif datasetSpec.type == "input":
|
|
236
|
+
if datasetSpec.containerName not in input_datasets:
|
|
237
|
+
input_datasets.append(datasetSpec.containerName)
|
|
238
|
+
elif datasetSpec.type == "output":
|
|
239
|
+
if datasetSpec.containerName not in output_datasets:
|
|
240
|
+
output_datasets.append(datasetSpec.containerName)
|
|
241
|
+
# process summary
|
|
242
|
+
if datasetSpec.isMasterInput():
|
|
243
|
+
if datasetSpec.status == "removed":
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
n_total_jobs += datasetSpec.nFiles
|
|
247
|
+
n_succeeded_jobs += datasetSpec.nFilesFinished
|
|
248
|
+
n_failed_jobs += datasetSpec.nFilesFailed
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
else:
|
|
252
|
+
input_str = "Points"
|
|
253
|
+
cancelled_str = "Unprocessed"
|
|
254
|
+
n_total_jobs = taskSpec.get_total_num_jobs()
|
|
255
|
+
event_stat = self.taskBufferIF.get_event_statistics(taskSpec.jediTaskID)
|
|
256
|
+
if event_stat is not None:
|
|
257
|
+
n_succeeded_jobs = event_stat.get(EventServiceUtils.ST_finished, 0)
|
|
258
|
+
n_failed_jobs = event_stat.get(EventServiceUtils.ST_failed, 0)
|
|
259
|
+
try:
|
|
260
|
+
n_cancelled_jobs = n_total_jobs - n_succeeded_jobs - n_failed_jobs
|
|
261
|
+
except Exception:
|
|
262
|
+
pass
|
|
263
|
+
if n_succeeded_jobs == n_total_jobs:
|
|
264
|
+
msg_succeeded = "All Succeeded"
|
|
265
|
+
else:
|
|
266
|
+
msg_succeeded = "Succeeded"
|
|
267
|
+
input_datasets.sort()
|
|
268
|
+
output_datasets.sort()
|
|
269
|
+
log_datasets.sort()
|
|
270
|
+
dataset_summary = ""
|
|
271
|
+
for tmpDS in input_datasets:
|
|
272
|
+
dataset_summary += f"In : {tmpDS}\n"
|
|
273
|
+
for tmpDS in output_datasets:
|
|
274
|
+
dataset_summary += f"Out : {tmpDS}\n"
|
|
275
|
+
for tmpDS in log_datasets:
|
|
276
|
+
dataset_summary += f"Log : {tmpDS}\n"
|
|
277
|
+
dataset_summary = dataset_summary[:-1]
|
|
278
|
+
|
|
279
|
+
# CLI param
|
|
280
|
+
if "cliParams" in self.taskParamMap:
|
|
281
|
+
cli_parameters = self.taskParamMap["cliParams"]
|
|
282
|
+
else:
|
|
283
|
+
cli_parameters = None
|
|
284
|
+
|
|
285
|
+
# make message
|
|
286
|
+
head = html_head.format(title="Task summary notification")
|
|
287
|
+
body = jedi_task_html_body.format(
|
|
288
|
+
jedi_task_id=taskSpec.jediTaskID,
|
|
289
|
+
creation_time=taskSpec.creationDate,
|
|
290
|
+
end_time=taskSpec.endTime,
|
|
291
|
+
task_status=taskSpec.status,
|
|
292
|
+
error_dialog=self.removeTags(taskSpec.errorDialog),
|
|
293
|
+
command=cli_parameters,
|
|
294
|
+
n_total=n_total_jobs,
|
|
295
|
+
n_succeeded=n_succeeded_jobs,
|
|
296
|
+
n_failed=n_failed_jobs,
|
|
297
|
+
n_cancelled=n_cancelled_jobs,
|
|
298
|
+
carbon_succeeded=carbon_footprint["finished"],
|
|
299
|
+
carbon_failed=carbon_footprint["failed"],
|
|
300
|
+
carbon_cancelled=carbon_footprint["cancelled"],
|
|
301
|
+
carbon_total=carbon_footprint["total"],
|
|
302
|
+
datasets_in=input_datasets,
|
|
303
|
+
datasets_out=output_datasets,
|
|
304
|
+
datasets_log=log_datasets,
|
|
305
|
+
msg_succeeded=msg_succeeded,
|
|
306
|
+
input_str=input_str,
|
|
307
|
+
cancelled_str=cancelled_str,
|
|
308
|
+
)
|
|
309
|
+
message_html = head + body
|
|
310
|
+
|
|
311
|
+
message_plain = jedi_task_plain.format(
|
|
312
|
+
jedi_task_id=taskSpec.jediTaskID,
|
|
313
|
+
creation_time=taskSpec.creationDate,
|
|
314
|
+
end_time=taskSpec.endTime,
|
|
315
|
+
task_status=taskSpec.status,
|
|
316
|
+
error_dialog=self.removeTags(taskSpec.errorDialog),
|
|
317
|
+
command=cli_parameters,
|
|
318
|
+
n_total=n_total_jobs,
|
|
319
|
+
n_succeeded=n_succeeded_jobs,
|
|
320
|
+
n_failed=n_failed_jobs,
|
|
321
|
+
n_cancelled=n_cancelled_jobs,
|
|
322
|
+
carbon_succeeded=carbon_footprint["finished"],
|
|
323
|
+
carbon_failed=carbon_footprint["failed"],
|
|
324
|
+
carbon_cancelled=carbon_footprint["cancelled"],
|
|
325
|
+
carbon_total=carbon_footprint["total"],
|
|
326
|
+
dataset_summary=dataset_summary,
|
|
327
|
+
msg_succeeded=msg_succeeded,
|
|
328
|
+
input_str=input_str,
|
|
329
|
+
cancelled_str=cancelled_str,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
subject = f"JEDI notification for TaskID:{taskSpec.jediTaskID} ({n_succeeded_jobs}/{n_total_jobs} {msg_succeeded})"
|
|
333
|
+
|
|
334
|
+
# return
|
|
335
|
+
return message_html, message_plain, subject
|
|
336
|
+
|
|
337
|
+
# get email
|
|
338
|
+
def getEmail(self, user_name, vo, tmp_logger):
|
|
339
|
+
# return to suppress mail
|
|
340
|
+
ret_suppressed = None
|
|
341
|
+
# get DN
|
|
342
|
+
tmp_logger.debug(f"getting email for {user_name}")
|
|
343
|
+
|
|
344
|
+
# get email from PANDAMETA DB
|
|
345
|
+
mail_address_db, dn, db_uptime = self.taskBufferIF.getEmailAddr(user_name, withDN=True)
|
|
346
|
+
tmp_logger.debug(f"email from MetaDB : {mail_address_db}")
|
|
347
|
+
# email notification is suppressed
|
|
348
|
+
not_send_mail = False
|
|
349
|
+
if mail_address_db is not None and mail_address_db.startswith("notsend"):
|
|
350
|
+
not_send_mail = True
|
|
351
|
+
# DN is unavailable
|
|
352
|
+
if dn in ["", None]:
|
|
353
|
+
# there will be no email
|
|
354
|
+
tmp_logger.debug("DN is empty")
|
|
355
|
+
else:
|
|
356
|
+
# avoid too frequent lookup
|
|
357
|
+
if db_uptime is not None and naive_utcnow() - db_uptime < datetime.timedelta(hours=1):
|
|
358
|
+
tmp_logger.debug("no lookup")
|
|
359
|
+
if not_send_mail or mail_address_db in [None, ""]:
|
|
360
|
+
return ret_suppressed
|
|
361
|
+
else:
|
|
362
|
+
return mail_address_db.split(":")[-1]
|
|
363
|
+
else:
|
|
364
|
+
# get email from DQ2
|
|
365
|
+
tmp_logger.debug(f"getting email using dq2Info.finger({dn})")
|
|
366
|
+
n_tries = 3
|
|
367
|
+
for iDDMTry in range(n_tries):
|
|
368
|
+
try:
|
|
369
|
+
user_info = self.ddmIF.getInterface(vo).finger(dn)
|
|
370
|
+
mail_address = user_info["email"]
|
|
371
|
+
tmp_logger.debug(f"email from DQ2 : {mail_address}")
|
|
372
|
+
if mail_address is None:
|
|
373
|
+
mail_address = ""
|
|
374
|
+
# make email field to update DB
|
|
375
|
+
mail_addr_to_db = ""
|
|
376
|
+
if not_send_mail:
|
|
377
|
+
mail_addr_to_db += "notsend:"
|
|
378
|
+
mail_addr_to_db += mail_address
|
|
379
|
+
# update database
|
|
380
|
+
tmp_logger.debug(f"update email to {mail_addr_to_db}")
|
|
381
|
+
self.taskBufferIF.setEmailAddr(user_name, mail_addr_to_db)
|
|
382
|
+
|
|
383
|
+
if not_send_mail or mail_address == "":
|
|
384
|
+
return ret_suppressed
|
|
385
|
+
return mail_address
|
|
386
|
+
except Exception:
|
|
387
|
+
if iDDMTry + 1 < n_tries:
|
|
388
|
+
tmp_logger.debug(f"sleep for retry {iDDMTry}/{n_tries}")
|
|
389
|
+
time.sleep(10)
|
|
390
|
+
else:
|
|
391
|
+
err_type, err_value = sys.exc_info()[:2]
|
|
392
|
+
tmp_logger.error(f"{err_type}:{err_value}")
|
|
393
|
+
# not send email
|
|
394
|
+
return ret_suppressed
|
|
395
|
+
|
|
396
|
+
# remove tags
|
|
397
|
+
def removeTags(self, tmp_str):
|
|
398
|
+
try:
|
|
399
|
+
if tmp_str is not None:
|
|
400
|
+
tmp_str = re.sub(">[^<]+<", "><", tmp_str)
|
|
401
|
+
tmp_str = re.sub("<[^<]+>", "", tmp_str)
|
|
402
|
+
except Exception:
|
|
403
|
+
pass
|
|
404
|
+
return tmp_str
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
from pandajedi.jedicore import JediException
|
|
4
|
+
from pandajedi.jedirefine import RefinerUtils
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import idds.common.constants
|
|
8
|
+
import idds.common.utils
|
|
9
|
+
from idds.client.client import Client as iDDS_Client
|
|
10
|
+
except ImportError:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# send notification to external system for additional post-processing
|
|
15
|
+
def send_notification(taskBufferIF, ddmIF, taskSpec, tmpLog):
|
|
16
|
+
# send notification to external system
|
|
17
|
+
try:
|
|
18
|
+
taskParam = taskBufferIF.getTaskParamsWithID_JEDI(taskSpec.jediTaskID)
|
|
19
|
+
taskParamMap = RefinerUtils.decodeJSON(taskParam)
|
|
20
|
+
except Exception as e:
|
|
21
|
+
errStr = f"task param conversion from json failed with {str(e)}"
|
|
22
|
+
raise JediException.ExternalTempError(errStr)
|
|
23
|
+
if "outputPostProcessing" in taskParamMap and "system" in taskParamMap["outputPostProcessing"]:
|
|
24
|
+
if taskParamMap["outputPostProcessing"]["system"] == "idds":
|
|
25
|
+
try:
|
|
26
|
+
c = iDDS_Client(idds.common.utils.get_rest_host())
|
|
27
|
+
if taskParamMap["outputPostProcessing"]["type"] == "active_learning":
|
|
28
|
+
for datasetSpec in taskSpec.datasetSpecList:
|
|
29
|
+
if datasetSpec.type != "output":
|
|
30
|
+
continue
|
|
31
|
+
data = copy.copy(taskParamMap["outputPostProcessing"]["data"])
|
|
32
|
+
tmp_scope, tmp_name = ddmIF.extract_scope(datasetSpec.datasetName)
|
|
33
|
+
data["workload_id"] = taskSpec.jediTaskID
|
|
34
|
+
req = {
|
|
35
|
+
"scope": tmp_scope,
|
|
36
|
+
"name": tmp_name,
|
|
37
|
+
"requester": "panda",
|
|
38
|
+
"request_type": idds.common.constants.RequestType.ActiveLearning,
|
|
39
|
+
"transform_tag": idds.common.constants.RequestType.ActiveLearning.value,
|
|
40
|
+
"status": idds.common.constants.RequestStatus.New,
|
|
41
|
+
"priority": 0,
|
|
42
|
+
"lifetime": 30,
|
|
43
|
+
"request_metadata": data,
|
|
44
|
+
}
|
|
45
|
+
tmpLog.debug(f"req {str(req)}")
|
|
46
|
+
ret = c.add_request(**req)
|
|
47
|
+
tmpLog.debug(f"got requestID={str(ret)}")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
errStr = f"iDDS failed with {str(e)}"
|
|
50
|
+
raise JediException.ExternalTempError(errStr)
|