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,572 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from threading import Lock
|
|
9
|
+
|
|
10
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# replacement for commands
|
|
14
|
+
def commands_get_status_output(com):
|
|
15
|
+
data = ""
|
|
16
|
+
try:
|
|
17
|
+
p = subprocess.Popen(
|
|
18
|
+
com,
|
|
19
|
+
shell=True,
|
|
20
|
+
universal_newlines=True,
|
|
21
|
+
stdout=subprocess.PIPE,
|
|
22
|
+
stderr=subprocess.STDOUT,
|
|
23
|
+
)
|
|
24
|
+
data, unused_err = p.communicate()
|
|
25
|
+
retcode = p.poll()
|
|
26
|
+
if retcode:
|
|
27
|
+
ex = subprocess.CalledProcessError(retcode, com)
|
|
28
|
+
raise ex
|
|
29
|
+
status = 0
|
|
30
|
+
except subprocess.CalledProcessError as ex:
|
|
31
|
+
status = ex.returncode
|
|
32
|
+
if data[-1:] == "\n":
|
|
33
|
+
data = data[:-1]
|
|
34
|
+
return status, data
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# extract name from DN
|
|
38
|
+
def clean_user_id(id):
|
|
39
|
+
try:
|
|
40
|
+
up = re.compile("/(DC|O|OU|C|L)=[^\/]+")
|
|
41
|
+
username = up.sub("", id)
|
|
42
|
+
up2 = re.compile("/CN=[0-9]+")
|
|
43
|
+
username = up2.sub("", username)
|
|
44
|
+
up3 = re.compile(" [0-9]+")
|
|
45
|
+
username = up3.sub("", username)
|
|
46
|
+
up4 = re.compile("_[0-9]+")
|
|
47
|
+
username = up4.sub("", username)
|
|
48
|
+
username = username.replace("/CN=proxy", "")
|
|
49
|
+
username = username.replace("/CN=limited proxy", "")
|
|
50
|
+
username = username.replace("limited proxy", "")
|
|
51
|
+
username = re.sub("/CN=Robot:[^/]+", "", username)
|
|
52
|
+
username = re.sub("/CN=nickname:[^/]+", "", username)
|
|
53
|
+
pat = re.compile(".*/CN=([^\/]+)/CN=([^\/]+)")
|
|
54
|
+
mat = pat.match(username)
|
|
55
|
+
if mat:
|
|
56
|
+
username = mat.group(2)
|
|
57
|
+
else:
|
|
58
|
+
username = username.replace("/CN=", "")
|
|
59
|
+
if username.lower().find("/email") > 0:
|
|
60
|
+
username = username[: username.lower().find("/email")]
|
|
61
|
+
pat = re.compile(".*(limited.*proxy).*")
|
|
62
|
+
mat = pat.match(username)
|
|
63
|
+
if mat:
|
|
64
|
+
username = mat.group(1)
|
|
65
|
+
username = username.replace("(", "")
|
|
66
|
+
username = username.replace(")", "")
|
|
67
|
+
username = username.replace("'", "")
|
|
68
|
+
name_wo_email = re.sub(r" [a-z][\w\.-]+@[\w\.-]+(?:\.\w+)+", "", username).strip()
|
|
69
|
+
if " " in name_wo_email:
|
|
70
|
+
username = name_wo_email
|
|
71
|
+
return username
|
|
72
|
+
except Exception:
|
|
73
|
+
return id
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# extract bare string from DN
|
|
77
|
+
def get_bare_dn(dn, keep_proxy=False, keep_digits=True):
|
|
78
|
+
dn = re.sub("/CN=limited proxy", "", dn)
|
|
79
|
+
if keep_proxy:
|
|
80
|
+
dn = re.sub("/CN=proxy(/CN=proxy)+", "/CN=proxy", dn)
|
|
81
|
+
else:
|
|
82
|
+
dn = re.sub("(/CN=proxy)+", "", dn)
|
|
83
|
+
if not keep_digits:
|
|
84
|
+
dn = re.sub(r"(/CN=\d+)+$", "", dn)
|
|
85
|
+
return dn
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# extract id string from DN
|
|
89
|
+
def get_id_from_dn(dn, keep_proxy=False, keep_digits=True):
|
|
90
|
+
m = re.search("/CN=nickname:([^/]+)", dn)
|
|
91
|
+
if m:
|
|
92
|
+
return m.group(1)
|
|
93
|
+
return get_bare_dn(dn, keep_proxy, keep_digits)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_distinguished_name_list(distinguished_name: str) -> list:
|
|
97
|
+
"""
|
|
98
|
+
Get a list of possible distinguished names from a string, including legacy and RFC formats.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
distinguished_name (str): The distinguished name string.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
list: A list of possible distinguished names.
|
|
105
|
+
"""
|
|
106
|
+
name_list = []
|
|
107
|
+
# characters to be escaped in RFC format
|
|
108
|
+
trans_table = str.maketrans({",": r"\,", "+": r"\+", '"': r"\"", "\\": r"\\", "<": r"\<", ">": r"\>", ";": r"\;"})
|
|
109
|
+
# loop over distinguished name and without-CN form
|
|
110
|
+
for tmp_name in [distinguished_name, get_bare_dn(distinguished_name, keep_digits=False)]:
|
|
111
|
+
name_list.append(tmp_name)
|
|
112
|
+
# replace backslashes with commas and reverse substrings to be converted to RFC format
|
|
113
|
+
tmp_list = [s.translate(trans_table) for s in tmp_name.split("/") if s]
|
|
114
|
+
name_list.append(",".join(tmp_list[::-1]))
|
|
115
|
+
return name_list
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def normalize_cpu_model(cpu_model):
|
|
119
|
+
cpu_model = cpu_model.upper()
|
|
120
|
+
# Remove GHz, cache sizes, and redundant words
|
|
121
|
+
cpu_model = re.sub(r"@\s*\d+(\.\d+)?\s*GHZ", "", cpu_model)
|
|
122
|
+
cpu_model = re.sub(r"\d+\s*KB", "", cpu_model)
|
|
123
|
+
cpu_model = re.sub(r"CORE PROCESSOR\s*\([^)]*\)", "", cpu_model)
|
|
124
|
+
cpu_model = re.sub(r"\b\d+-CORE\b", "", cpu_model)
|
|
125
|
+
cpu_model = re.sub(r"CPU|CORE|PROCESSOR|SCALABLE|\(R\)|\(TM\)", "", cpu_model)
|
|
126
|
+
cpu_model = re.sub(r"\s+", " ", cpu_model)
|
|
127
|
+
cpu_model = cpu_model.strip()
|
|
128
|
+
return cpu_model
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def clean_host_name(host_name):
|
|
132
|
+
# If the worker node comes in the slot1@worker1.example.com format, we remove the slot1@ part
|
|
133
|
+
match = re.search(r"@(.+)", host_name)
|
|
134
|
+
host_name = match.group(1) if match else host_name
|
|
135
|
+
|
|
136
|
+
# Special handling for ATLAS worker nodes to extract the third field of the hostname, since the first 2 fields are not unique
|
|
137
|
+
# e.g. atlprd55-xyz-<third_field>.cern.ch
|
|
138
|
+
match = re.match(r"^atlprd\d+-[^-]+-([^.]+\.cern\.ch)$", host_name)
|
|
139
|
+
host_name = match.group(1) if match else host_name
|
|
140
|
+
|
|
141
|
+
return host_name
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# resolve string bool
|
|
145
|
+
def resolve_bool(param):
|
|
146
|
+
if isinstance(param, bool):
|
|
147
|
+
return param
|
|
148
|
+
if param == "True":
|
|
149
|
+
return True
|
|
150
|
+
if param == "False":
|
|
151
|
+
return False
|
|
152
|
+
return param
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# cached object
|
|
156
|
+
class CachedObject:
|
|
157
|
+
# constructor
|
|
158
|
+
def __init__(self, name, time_interval, update_func, log_stream):
|
|
159
|
+
# name
|
|
160
|
+
self.name = name
|
|
161
|
+
# cached object
|
|
162
|
+
self.cachedObj = None
|
|
163
|
+
# datetime of last updated
|
|
164
|
+
self.lastUpdated = naive_utcnow()
|
|
165
|
+
# update frequency
|
|
166
|
+
self.timeInterval = datetime.timedelta(seconds=time_interval)
|
|
167
|
+
# lock
|
|
168
|
+
self.lock = Lock()
|
|
169
|
+
# function to update object
|
|
170
|
+
self.updateFunc = update_func
|
|
171
|
+
# log
|
|
172
|
+
self.log_stream = log_stream
|
|
173
|
+
|
|
174
|
+
# update obj
|
|
175
|
+
def update(self):
|
|
176
|
+
# lock
|
|
177
|
+
self.lock.acquire()
|
|
178
|
+
# get current datetime
|
|
179
|
+
current = naive_utcnow()
|
|
180
|
+
# update if old
|
|
181
|
+
if self.cachedObj is None or current - self.lastUpdated > self.timeInterval:
|
|
182
|
+
self.log_stream.debug(f"PID={os.getpid()} renewing {self.name} cache")
|
|
183
|
+
try:
|
|
184
|
+
tmp_stat, tmp_out = self.updateFunc()
|
|
185
|
+
self.log_stream.debug(f"PID={os.getpid()} got for {self.name} {tmp_stat} {str(tmp_out)}")
|
|
186
|
+
if tmp_stat:
|
|
187
|
+
self.cachedObj = tmp_out
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.log_stream.error(f"PID={os.getpid()} failed to renew {self.name} due to {str(e)}")
|
|
190
|
+
self.lastUpdated = current
|
|
191
|
+
# release
|
|
192
|
+
self.lock.release()
|
|
193
|
+
# return
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# contains
|
|
197
|
+
def __contains__(self, item):
|
|
198
|
+
self.update()
|
|
199
|
+
contains = False
|
|
200
|
+
try:
|
|
201
|
+
contains = item in self.cachedObj
|
|
202
|
+
except TypeError:
|
|
203
|
+
pass
|
|
204
|
+
return contains
|
|
205
|
+
|
|
206
|
+
# get item
|
|
207
|
+
def __getitem__(self, name):
|
|
208
|
+
self.update()
|
|
209
|
+
return self.cachedObj[name]
|
|
210
|
+
|
|
211
|
+
# get method
|
|
212
|
+
def get(self, *var):
|
|
213
|
+
return self.cachedObj.get(*var)
|
|
214
|
+
|
|
215
|
+
# get object
|
|
216
|
+
def get_object(self):
|
|
217
|
+
self.lock.acquire()
|
|
218
|
+
return self.cachedObj
|
|
219
|
+
|
|
220
|
+
# release object
|
|
221
|
+
def release_object(self):
|
|
222
|
+
self.lock.release()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# dictionary of caches
|
|
226
|
+
class CacheDict:
|
|
227
|
+
"""
|
|
228
|
+
Dictionary of caches with periodic cleanup
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
# constructor
|
|
232
|
+
def __init__(self, update_interval=10, cleanup_interval=60):
|
|
233
|
+
self.idx = 0
|
|
234
|
+
self.lock = Lock()
|
|
235
|
+
self.cache_dict = {}
|
|
236
|
+
self.update_interval = datetime.timedelta(minutes=update_interval)
|
|
237
|
+
self.cleanup_interval = datetime.timedelta(minutes=cleanup_interval)
|
|
238
|
+
self.last_cleanup = naive_utcnow()
|
|
239
|
+
|
|
240
|
+
def cleanup(self, tmp_log):
|
|
241
|
+
"""
|
|
242
|
+
Cleanup caches
|
|
243
|
+
:param tmp_log: logger
|
|
244
|
+
"""
|
|
245
|
+
with self.lock:
|
|
246
|
+
current = naive_utcnow()
|
|
247
|
+
if current - self.last_cleanup > self.cleanup_interval:
|
|
248
|
+
for name in list(self.cache_dict):
|
|
249
|
+
cache = self.cache_dict[name]
|
|
250
|
+
if current - cache["last_updated"] > self.cleanup_interval:
|
|
251
|
+
tmp_log.debug(f"""deleting cache #{self.cache_dict[name]["idx"]}""")
|
|
252
|
+
del self.cache_dict[name]
|
|
253
|
+
self.last_cleanup = current
|
|
254
|
+
|
|
255
|
+
def get(self, name, tmp_log, update_func, *update_args, **update_kwargs):
|
|
256
|
+
"""
|
|
257
|
+
Get updated object
|
|
258
|
+
:param name: name of cache
|
|
259
|
+
:param tmp_log: logger
|
|
260
|
+
:param update_func: function to update object
|
|
261
|
+
:param update_args: arguments for update function
|
|
262
|
+
:param update_kwargs: keyword arguments for update function
|
|
263
|
+
:return: object or None
|
|
264
|
+
"""
|
|
265
|
+
self.cleanup(tmp_log)
|
|
266
|
+
with self.lock:
|
|
267
|
+
obj = self.cache_dict.get(name)
|
|
268
|
+
current = naive_utcnow()
|
|
269
|
+
if not obj:
|
|
270
|
+
tmp_log.debug(f"creating new cache #{self.idx}")
|
|
271
|
+
# create new cache
|
|
272
|
+
obj = {
|
|
273
|
+
"obj": update_func(*update_args, **update_kwargs),
|
|
274
|
+
"last_updated": current,
|
|
275
|
+
"update_func": update_func,
|
|
276
|
+
"update_args": update_args,
|
|
277
|
+
"update_kwargs": update_kwargs,
|
|
278
|
+
"idx": self.idx,
|
|
279
|
+
}
|
|
280
|
+
self.cache_dict[name] = obj
|
|
281
|
+
self.idx += 1
|
|
282
|
+
else:
|
|
283
|
+
# update if old
|
|
284
|
+
if current - obj["last_updated"] > self.update_interval:
|
|
285
|
+
tmp_log.debug(f"""updating cache #{obj["idx"]}""")
|
|
286
|
+
obj["obj"] = obj["update_func"](*obj["update_args"], **obj["update_kwargs"])
|
|
287
|
+
obj["last_updated"] = current
|
|
288
|
+
else:
|
|
289
|
+
tmp_log.debug(f"""reusing cache #{obj["idx"]}""")
|
|
290
|
+
return obj["obj"]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# convert datetime to string
|
|
294
|
+
class NonJsonObjectEncoder(json.JSONEncoder):
|
|
295
|
+
def default(self, obj):
|
|
296
|
+
if isinstance(obj, datetime.datetime):
|
|
297
|
+
return {"_datetime_object": obj.strftime("%Y-%m-%d %H:%M:%S.%f")}
|
|
298
|
+
return json.JSONEncoder.default(self, obj)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# hook for json decoder
|
|
302
|
+
def as_python_object(dct):
|
|
303
|
+
if "_datetime_object" in dct:
|
|
304
|
+
return datetime.datetime.strptime(str(dct["_datetime_object"]), "%Y-%m-%d %H:%M:%S.%f")
|
|
305
|
+
return dct
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# get effective file size
|
|
309
|
+
def getEffectiveFileSize(fsize, startEvent, endEvent, nEvents):
|
|
310
|
+
inMB = 1024 * 1024
|
|
311
|
+
if fsize in [None, 0]:
|
|
312
|
+
# use dummy size for pseudo input
|
|
313
|
+
effectiveFsize = inMB
|
|
314
|
+
elif nEvents is not None and startEvent is not None and endEvent is not None:
|
|
315
|
+
# take event range into account
|
|
316
|
+
effectiveFsize = int(float(fsize) * float(endEvent - startEvent + 1) / float(nEvents))
|
|
317
|
+
else:
|
|
318
|
+
effectiveFsize = fsize
|
|
319
|
+
# use dummy size if input is too small
|
|
320
|
+
if effectiveFsize == 0:
|
|
321
|
+
effectiveFsize = inMB
|
|
322
|
+
# in MB
|
|
323
|
+
effectiveFsize = float(effectiveFsize) / inMB
|
|
324
|
+
# return
|
|
325
|
+
return effectiveFsize
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# get effective number of events
|
|
329
|
+
def getEffectiveNumEvents(startEvent, endEvent, nEvents):
|
|
330
|
+
if endEvent is not None and startEvent is not None:
|
|
331
|
+
evtCounts = endEvent - startEvent + 1
|
|
332
|
+
if evtCounts > 0:
|
|
333
|
+
return evtCounts
|
|
334
|
+
return 1
|
|
335
|
+
if nEvents is not None:
|
|
336
|
+
return nEvents
|
|
337
|
+
return 1
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# get memory usage
|
|
341
|
+
def getMemoryUsage():
|
|
342
|
+
try:
|
|
343
|
+
t = open(f"/proc/{os.getpid()}/status")
|
|
344
|
+
v = t.read()
|
|
345
|
+
t.close()
|
|
346
|
+
value = 0
|
|
347
|
+
for line in v.split("\n"):
|
|
348
|
+
if line.startswith("VmRSS"):
|
|
349
|
+
items = line.split()
|
|
350
|
+
value = int(items[1])
|
|
351
|
+
if items[2] in ["kB", "KB"]:
|
|
352
|
+
value /= 1024
|
|
353
|
+
elif items[2] in ["mB", "MB"]:
|
|
354
|
+
pass
|
|
355
|
+
break
|
|
356
|
+
return int(value)
|
|
357
|
+
except Exception:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# check process
|
|
362
|
+
def checkProcess(pid):
|
|
363
|
+
return os.path.exists(f"/proc/{pid}/status")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# offset for walltime
|
|
367
|
+
wallTimeOffset = 10 * 60
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# convert config parameters
|
|
371
|
+
def convert_config_params(itemStr):
|
|
372
|
+
items = itemStr.split(":")
|
|
373
|
+
newItems = []
|
|
374
|
+
for item in items:
|
|
375
|
+
if item == "":
|
|
376
|
+
newItems.append(None)
|
|
377
|
+
elif "," in item:
|
|
378
|
+
newItems.append(item.split(","))
|
|
379
|
+
else:
|
|
380
|
+
try:
|
|
381
|
+
newItems.append(int(item))
|
|
382
|
+
except Exception:
|
|
383
|
+
newItems.append(item)
|
|
384
|
+
return newItems
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
# parse init params
|
|
388
|
+
def parse_init_params(par):
|
|
389
|
+
if isinstance(par, list):
|
|
390
|
+
return par
|
|
391
|
+
try:
|
|
392
|
+
return par.split("|")
|
|
393
|
+
except Exception:
|
|
394
|
+
return [par]
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# get config param for vo and prodSourceLabel
|
|
398
|
+
def getConfigParam(configStr, vo, sourceLabel):
|
|
399
|
+
try:
|
|
400
|
+
for _ in configStr.split(","):
|
|
401
|
+
items = configStr.split(":")
|
|
402
|
+
vos = items[0].split("|")
|
|
403
|
+
sourceLabels = items[1].split("|")
|
|
404
|
+
if vo not in ["", "any"] and vo not in vos and None not in vos and "any" not in vos and "" not in vos:
|
|
405
|
+
continue
|
|
406
|
+
if (
|
|
407
|
+
sourceLabel not in ["", "any"]
|
|
408
|
+
and sourceLabel not in sourceLabels
|
|
409
|
+
and None not in sourceLabels
|
|
410
|
+
and "any" not in sourceLabels
|
|
411
|
+
and "" not in sourceLabels
|
|
412
|
+
):
|
|
413
|
+
continue
|
|
414
|
+
return ",".join(items[2:])
|
|
415
|
+
except Exception:
|
|
416
|
+
pass
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# get percentile until numpy 1.5.X becomes available
|
|
421
|
+
def percentile(inList, percent, idMap):
|
|
422
|
+
inList = sorted(copy.copy(inList))
|
|
423
|
+
k = (len(inList) - 1) * float(percent) / 100
|
|
424
|
+
f = math.floor(k)
|
|
425
|
+
c = math.ceil(k)
|
|
426
|
+
if f == c:
|
|
427
|
+
retVal = inList[int(f)]
|
|
428
|
+
return retVal, [retVal]
|
|
429
|
+
val0 = inList[int(f)]
|
|
430
|
+
val1 = inList[int(c)]
|
|
431
|
+
d0 = val0 * (c - k)
|
|
432
|
+
d1 = val1 * (k - f)
|
|
433
|
+
retVal = d0 + d1
|
|
434
|
+
return retVal, [val0, val1]
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# get max walltime and cpu count
|
|
438
|
+
def getJobMaxWalltime(taskSpec, inputChunk, totalMasterEvents, jobSpec, siteSpec):
|
|
439
|
+
try:
|
|
440
|
+
if taskSpec.getCpuTime() is None:
|
|
441
|
+
# use PQ maxtime when CPU time is not defined
|
|
442
|
+
jobSpec.maxWalltime = siteSpec.maxtime
|
|
443
|
+
jobSpec.maxCpuCount = siteSpec.maxtime
|
|
444
|
+
else:
|
|
445
|
+
jobSpec.maxWalltime = taskSpec.getCpuTime()
|
|
446
|
+
if jobSpec.maxWalltime is not None and jobSpec.maxWalltime > 0:
|
|
447
|
+
jobSpec.maxWalltime *= totalMasterEvents
|
|
448
|
+
if siteSpec.coreCount > 0:
|
|
449
|
+
jobSpec.maxWalltime /= float(siteSpec.coreCount)
|
|
450
|
+
if siteSpec.corepower not in [0, None]:
|
|
451
|
+
jobSpec.maxWalltime /= siteSpec.corepower
|
|
452
|
+
if taskSpec.cpuEfficiency not in [None, 0]:
|
|
453
|
+
jobSpec.maxWalltime /= float(taskSpec.cpuEfficiency) / 100.0
|
|
454
|
+
if taskSpec.baseWalltime is not None:
|
|
455
|
+
jobSpec.maxWalltime += taskSpec.baseWalltime
|
|
456
|
+
jobSpec.maxWalltime = int(jobSpec.maxWalltime)
|
|
457
|
+
if taskSpec.useHS06():
|
|
458
|
+
jobSpec.maxCpuCount = jobSpec.maxWalltime
|
|
459
|
+
except Exception:
|
|
460
|
+
pass
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# use direct IO for job
|
|
464
|
+
def use_direct_io_for_job(task_spec, site_spec, input_chunk):
|
|
465
|
+
# not for merging
|
|
466
|
+
if input_chunk and input_chunk.isMerging:
|
|
467
|
+
return False
|
|
468
|
+
# always
|
|
469
|
+
if site_spec.always_use_direct_io():
|
|
470
|
+
return True
|
|
471
|
+
# force copy-to-scratch
|
|
472
|
+
if task_spec.useLocalIO():
|
|
473
|
+
return False
|
|
474
|
+
# depends on task and site specs
|
|
475
|
+
if task_spec.allowInputLAN() is not None and site_spec.isDirectIO():
|
|
476
|
+
return True
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# stopwatch
|
|
481
|
+
class StopWatch:
|
|
482
|
+
"""Utility class to measure timing information."""
|
|
483
|
+
|
|
484
|
+
def __init__(self, identifier: str = None):
|
|
485
|
+
self.start_time = datetime.datetime.now()
|
|
486
|
+
self.checkpoint = self.start_time
|
|
487
|
+
self.step_name = None
|
|
488
|
+
self.identifier = identifier
|
|
489
|
+
|
|
490
|
+
def reset(self):
|
|
491
|
+
"""Reset the stopwatch."""
|
|
492
|
+
self.start_time = datetime.datetime.now()
|
|
493
|
+
self.checkpoint = self.start_time
|
|
494
|
+
self.step_name = None
|
|
495
|
+
|
|
496
|
+
def get_elapsed_time(self, new_step_name: str) -> str:
|
|
497
|
+
"""Get the elapsed time since the stopwatch was started and the duration since the last checkpoint.
|
|
498
|
+
:param new_step_name: The name of the next step.
|
|
499
|
+
Returns:
|
|
500
|
+
str: A string with the elapsed time and the duration since the last checkpoint.
|
|
501
|
+
"""
|
|
502
|
+
now = datetime.datetime.now()
|
|
503
|
+
total_delta = now - self.start_time
|
|
504
|
+
duration_delta = now - self.checkpoint
|
|
505
|
+
return_str = ""
|
|
506
|
+
if self.identifier:
|
|
507
|
+
return_str += f"{self.identifier}: "
|
|
508
|
+
return_str += f"elapsed {total_delta.seconds}.{int(total_delta.microseconds / 1000):03d} sec. "
|
|
509
|
+
if self.step_name is not None:
|
|
510
|
+
return_str += f"{self.step_name} took {duration_delta.seconds}.{int(duration_delta.microseconds / 1000):03d} sec. "
|
|
511
|
+
if new_step_name:
|
|
512
|
+
return_str += f"{new_step_name} started."
|
|
513
|
+
else:
|
|
514
|
+
return_str += "done."
|
|
515
|
+
self.checkpoint = now
|
|
516
|
+
self.step_name = new_step_name
|
|
517
|
+
return return_str
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# construct execution comment to reassign a task
|
|
521
|
+
def make_reassign_comment(site: str = None, cloud: str = None, nucleus: str = None, mode: str = None) -> str:
|
|
522
|
+
"""
|
|
523
|
+
Construct execution comment to reassign a task to a site, cloud or nucleus with different modes.
|
|
524
|
+
|
|
525
|
+
:param site: target site for reassignment
|
|
526
|
+
:param cloud: target cloud for reassignment
|
|
527
|
+
:param nucleus: target nucleus for reassignment
|
|
528
|
+
:param mode: can be "nokill" to avoid killing running jobs and "soft" to trigger rebrokerage without killing running jobs
|
|
529
|
+
|
|
530
|
+
:return: execution comment for task reassignment
|
|
531
|
+
"""
|
|
532
|
+
# reassign to site, nucleus or cloud. Note that Prodsys use blah="" to trigger rebrokerage
|
|
533
|
+
if site is not None:
|
|
534
|
+
comment = f"site:{site}:y" # set 'y' to go back to oldStatus bypassing rebrokerage
|
|
535
|
+
else:
|
|
536
|
+
if nucleus is not None:
|
|
537
|
+
comment = f"nucleus:{nucleus}"
|
|
538
|
+
else:
|
|
539
|
+
comment = f"cloud:{cloud}"
|
|
540
|
+
# set 'y' to trigger rebrokerage
|
|
541
|
+
comment += ":n"
|
|
542
|
+
|
|
543
|
+
# set additional modes
|
|
544
|
+
if mode == "nokill":
|
|
545
|
+
comment += ":nokill reassign"
|
|
546
|
+
elif mode == "soft":
|
|
547
|
+
comment += ":soft reassign"
|
|
548
|
+
|
|
549
|
+
return comment
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
# parse execution comment to get task reassignment instructions
|
|
553
|
+
def parse_reassign_comment(comment: str) -> dict:
|
|
554
|
+
"""
|
|
555
|
+
Parse execution comment to get task reassignment instructions, including target site/cloud/nucleus, whether to go back to old status and additional modes.
|
|
556
|
+
|
|
557
|
+
:param comment: execution comment for task reassignment
|
|
558
|
+
|
|
559
|
+
:return: a dictionary with keys "target", "value", "back_to_old_status" and "mode" (if any)
|
|
560
|
+
"""
|
|
561
|
+
info = {"target": "site", "value": None, "back_to_old_status": False, "mode": None}
|
|
562
|
+
try:
|
|
563
|
+
items = comment.split(":")
|
|
564
|
+
if len(items) >= 3:
|
|
565
|
+
info["target"] = items[0]
|
|
566
|
+
info["value"] = items[1]
|
|
567
|
+
info["back_to_old_status"] = items[2] == "y"
|
|
568
|
+
if len(items) > 3:
|
|
569
|
+
info["mode"] = items[3]
|
|
570
|
+
except Exception:
|
|
571
|
+
pass
|
|
572
|
+
return info
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
email utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import smtplib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from pandacommon.pandalogger.PandaLogger import PandaLogger
|
|
9
|
+
|
|
10
|
+
from pandaserver.config import panda_config
|
|
11
|
+
|
|
12
|
+
# logger
|
|
13
|
+
_logger = PandaLogger().getLogger("MailUtils")
|
|
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
|
+
class MailUtils:
|
|
46
|
+
# constructor
|
|
47
|
+
def __init__(self):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
# main
|
|
51
|
+
def send(self, toAddr, mailSubject, mailBody):
|
|
52
|
+
_logger.debug("start SEND session")
|
|
53
|
+
try:
|
|
54
|
+
# remove duplicated address
|
|
55
|
+
listToAddr = []
|
|
56
|
+
newToAddr = ""
|
|
57
|
+
for tmpToAddr in toAddr.split(","):
|
|
58
|
+
if tmpToAddr not in listToAddr:
|
|
59
|
+
listToAddr.append(tmpToAddr)
|
|
60
|
+
newToAddr += f"{tmpToAddr},"
|
|
61
|
+
toAddr = newToAddr[:-1]
|
|
62
|
+
|
|
63
|
+
# make message
|
|
64
|
+
fromAdd = panda_config.emailSender
|
|
65
|
+
message = f"Subject: {mailSubject}\n" f"From: {fromAdd}\n" f"To: {toAddr}\n\n" f"{mailBody}"
|
|
66
|
+
message = self.addTailer(message)
|
|
67
|
+
|
|
68
|
+
# send mail
|
|
69
|
+
_logger.debug(f"send to {toAddr}\n{message}")
|
|
70
|
+
stderrLog = StderrLogger(_logger)
|
|
71
|
+
server = MySMTP(panda_config.emailSMTPsrv)
|
|
72
|
+
server.set_debuglevel(1)
|
|
73
|
+
server.set_log(stderrLog)
|
|
74
|
+
server.ehlo()
|
|
75
|
+
server.starttls()
|
|
76
|
+
# server.login(panda_config.emailLogin,panda_config.emailPass)
|
|
77
|
+
out = server.sendmail(fromAdd, listToAddr, message)
|
|
78
|
+
_logger.debug(out)
|
|
79
|
+
server.quit()
|
|
80
|
+
retVal = True
|
|
81
|
+
except Exception:
|
|
82
|
+
type, value, traceBack = sys.exc_info()
|
|
83
|
+
_logger.error(f"{type} {value}")
|
|
84
|
+
retVal = False
|
|
85
|
+
try:
|
|
86
|
+
server.reset_log()
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
_logger.debug("end SEND session")
|
|
90
|
+
return retVal
|
|
91
|
+
|
|
92
|
+
# add tailer
|
|
93
|
+
def addTailer(self, msg):
|
|
94
|
+
msg += """
|
|
95
|
+
Report Panda problems of any sort to
|
|
96
|
+
|
|
97
|
+
the eGroup for help request
|
|
98
|
+
hn-atlas-dist-analysis-help@cern.ch
|
|
99
|
+
|
|
100
|
+
the Discourse forum for distributed computing help
|
|
101
|
+
https://atlas-talk.web.cern.ch/c/distributed-computing-help
|
|
102
|
+
|
|
103
|
+
the Panda JIRA for software bug
|
|
104
|
+
https://its.cern.ch/jira/browse/ATLASPANDA
|
|
105
|
+
"""
|
|
106
|
+
return msg
|
|
File without changes
|