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.
Files changed (375) hide show
  1. panda_server_source-0.8.0.dist-info/METADATA +55 -0
  2. panda_server_source-0.8.0.dist-info/RECORD +375 -0
  3. panda_server_source-0.8.0.dist-info/WHEEL +4 -0
  4. panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
  5. pandajedi/__init__.py +0 -0
  6. pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
  7. pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
  8. pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
  9. pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
  10. pandajedi/jedibrokerage/GenJobBroker.py +381 -0
  11. pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
  12. pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
  13. pandajedi/jedibrokerage/__init__.py +0 -0
  14. pandajedi/jediconfig/__init__.py +0 -0
  15. pandajedi/jediconfig/jedi_config.py +40 -0
  16. pandajedi/jedicore/FactoryBase.py +182 -0
  17. pandajedi/jedicore/Interaction.py +433 -0
  18. pandajedi/jedicore/JediDBProxy.py +56 -0
  19. pandajedi/jedicore/JediDBProxyPool.py +37 -0
  20. pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
  21. pandajedi/jedicore/JediException.py +13 -0
  22. pandajedi/jedicore/JediTaskBuffer.py +984 -0
  23. pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
  24. pandajedi/jedicore/MsgWrapper.py +124 -0
  25. pandajedi/jedicore/ProcessUtils.py +34 -0
  26. pandajedi/jedicore/SiteCandidate.py +90 -0
  27. pandajedi/jedicore/ThreadUtils.py +205 -0
  28. pandajedi/jedicore/__init__.py +0 -0
  29. pandajedi/jediddm/AtlasDDMClient.py +1708 -0
  30. pandajedi/jediddm/DDMClientBase.py +16 -0
  31. pandajedi/jediddm/DDMInterface.py +83 -0
  32. pandajedi/jediddm/GenDDMClient.py +17 -0
  33. pandajedi/jediddm/__init__.py +0 -0
  34. pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
  35. pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
  36. pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
  37. pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
  38. pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
  39. pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
  40. pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
  41. pandajedi/jedidog/GenWatchDog.py +21 -0
  42. pandajedi/jedidog/JumboWatchDog.py +147 -0
  43. pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
  44. pandajedi/jedidog/WatchDogBase.py +53 -0
  45. pandajedi/jedidog/__init__.py +0 -0
  46. pandajedi/jediexec/reniceJEDI.py +25 -0
  47. pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
  48. pandajedi/jedigen/TaskGeneratorBase.py +15 -0
  49. pandajedi/jedigen/__init__.py +0 -0
  50. pandajedi/jedimsgprocessor/__init__.py +0 -0
  51. pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
  52. pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
  53. pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
  54. pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
  55. pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
  56. pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
  57. pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
  58. pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
  59. pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
  60. pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
  61. pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
  62. pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
  63. pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
  64. pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
  65. pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
  66. pandajedi/jediorder/ContentsFeeder.py +785 -0
  67. pandajedi/jediorder/JediDaemon.py +27 -0
  68. pandajedi/jediorder/JediKnight.py +55 -0
  69. pandajedi/jediorder/JediMaster.py +238 -0
  70. pandajedi/jediorder/JediMsgProcessor.py +32 -0
  71. pandajedi/jediorder/JobBroker.py +33 -0
  72. pandajedi/jediorder/JobGenerator.py +2763 -0
  73. pandajedi/jediorder/JobSplitter.py +308 -0
  74. pandajedi/jediorder/JobThrottler.py +28 -0
  75. pandajedi/jediorder/PostProcessor.py +166 -0
  76. pandajedi/jediorder/TaskBroker.py +268 -0
  77. pandajedi/jediorder/TaskCommando.py +334 -0
  78. pandajedi/jediorder/TaskGenerator.py +17 -0
  79. pandajedi/jediorder/TaskRefiner.py +611 -0
  80. pandajedi/jediorder/TaskSetupper.py +17 -0
  81. pandajedi/jediorder/WatchDog.py +79 -0
  82. pandajedi/jediorder/__init__.py +0 -0
  83. pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
  84. pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
  85. pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
  86. pandajedi/jedipprocess/GenPostProcessor.py +35 -0
  87. pandajedi/jedipprocess/MailTemplates.py +185 -0
  88. pandajedi/jedipprocess/PostProcessorBase.py +269 -0
  89. pandajedi/jedipprocess/__init__.py +0 -0
  90. pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
  91. pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
  92. pandajedi/jedirefine/GenTaskRefiner.py +61 -0
  93. pandajedi/jedirefine/RefinerUtils.py +154 -0
  94. pandajedi/jedirefine/TaskRefinerBase.py +996 -0
  95. pandajedi/jedirefine/__init__.py +0 -0
  96. pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
  97. pandajedi/jedisetup/GenTaskSetupper.py +16 -0
  98. pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
  99. pandajedi/jedisetup/TaskSetupperBase.py +15 -0
  100. pandajedi/jedisetup/__init__.py +0 -0
  101. pandajedi/jeditest/SchemaChecker.py +41 -0
  102. pandajedi/jeditest/addContWoSoTask.py +54 -0
  103. pandajedi/jeditest/addDataCarouselRequest.py +47 -0
  104. pandajedi/jeditest/addEventServiceTask.py +101 -0
  105. pandajedi/jeditest/addHpoTask.py +81 -0
  106. pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
  107. pandajedi/jeditest/addNonAtlasTask.py +71 -0
  108. pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
  109. pandajedi/jeditest/addTestTask.py +124 -0
  110. pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
  111. pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
  112. pandajedi/jeditest/brokerTest.py +78 -0
  113. pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
  114. pandajedi/jeditest/contentsFeederTest.py +28 -0
  115. pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
  116. pandajedi/jeditest/ddmTest.py +4 -0
  117. pandajedi/jeditest/jobGenForOneTask.py +38 -0
  118. pandajedi/jeditest/jobGeneratorTest.py +43 -0
  119. pandajedi/jeditest/jobSplitterTest.py +104 -0
  120. pandajedi/jeditest/jobThrottlerTest.py +22 -0
  121. pandajedi/jeditest/msgProcessorTest.py +9 -0
  122. pandajedi/jeditest/postProcessorTest.py +18 -0
  123. pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
  124. pandajedi/jeditest/resubmitTaskParam.py +20 -0
  125. pandajedi/jeditest/taskBrokerTest.py +22 -0
  126. pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
  127. pandajedi/jeditest/taskBufferTest.py +5 -0
  128. pandajedi/jeditest/taskCommandoTest.py +28 -0
  129. pandajedi/jeditest/taskRefinerTest.py +30 -0
  130. pandajedi/jeditest/testOneTaskToRefine.py +49 -0
  131. pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
  132. pandajedi/jeditest/testPandaDDMMap.py +62 -0
  133. pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
  134. pandajedi/jeditest/watchDogTest.py +25 -0
  135. pandajedi/jeditest/workQueueTest.py +22 -0
  136. pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
  137. pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
  138. pandajedi/jedithrottle/GenJobThrottler.py +26 -0
  139. pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
  140. pandajedi/jedithrottle/__init__.py +0 -0
  141. pandaserver/__init__.py +0 -0
  142. pandaserver/api/__init__.py +0 -0
  143. pandaserver/api/v1/__init__.py +0 -0
  144. pandaserver/api/v1/common.py +339 -0
  145. pandaserver/api/v1/credential_management_api.py +372 -0
  146. pandaserver/api/v1/data_carousel_api.py +264 -0
  147. pandaserver/api/v1/event_api.py +269 -0
  148. pandaserver/api/v1/file_server_api.py +942 -0
  149. pandaserver/api/v1/harvester_api.py +445 -0
  150. pandaserver/api/v1/http_client.py +166 -0
  151. pandaserver/api/v1/idds_api.py +180 -0
  152. pandaserver/api/v1/job_api.py +463 -0
  153. pandaserver/api/v1/metaconfig_api.py +122 -0
  154. pandaserver/api/v1/pilot_api.py +895 -0
  155. pandaserver/api/v1/statistics_api.py +207 -0
  156. pandaserver/api/v1/system_api.py +186 -0
  157. pandaserver/api/v1/task_api.py +1520 -0
  158. pandaserver/api/v1/tests/__init__.py +0 -0
  159. pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
  160. pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
  161. pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
  162. pandaserver/api/v1/tests/idds_api_tests.py +45 -0
  163. pandaserver/api/v1/tests/job_api_tests.py +112 -0
  164. pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
  165. pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
  166. pandaserver/api/v1/tests/system_api_tests.py +58 -0
  167. pandaserver/api/v1/tests/task_api_tests.py +590 -0
  168. pandaserver/api/v1/version.py +2 -0
  169. pandaserver/api/v1/workflow_api.py +136 -0
  170. pandaserver/brokerage/SiteMapper.py +406 -0
  171. pandaserver/brokerage/__init__.py +0 -0
  172. pandaserver/brokerage/broker.py +272 -0
  173. pandaserver/config/__init__.py +0 -0
  174. pandaserver/config/config_utils.py +15 -0
  175. pandaserver/config/daemon_config.py +29 -0
  176. pandaserver/config/mcp_config.py +38 -0
  177. pandaserver/config/panda_config.py +233 -0
  178. pandaserver/configurator/Carbon.py +65 -0
  179. pandaserver/configurator/Configurator.py +908 -0
  180. pandaserver/configurator/__init__.py +0 -0
  181. pandaserver/configurator/aux.py +73 -0
  182. pandaserver/daemons/__init__.py +0 -0
  183. pandaserver/daemons/master.py +110 -0
  184. pandaserver/daemons/master_systemd.py +79 -0
  185. pandaserver/daemons/scripts/__init__.py +0 -0
  186. pandaserver/daemons/scripts/add_main.py +264 -0
  187. pandaserver/daemons/scripts/add_sub.py +221 -0
  188. pandaserver/daemons/scripts/cache_pilots.py +47 -0
  189. pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
  190. pandaserver/daemons/scripts/carbon.py +50 -0
  191. pandaserver/daemons/scripts/configurator.py +94 -0
  192. pandaserver/daemons/scripts/copyArchive.py +1387 -0
  193. pandaserver/daemons/scripts/datasetManager.py +1154 -0
  194. pandaserver/daemons/scripts/dummy_test.py +25 -0
  195. pandaserver/daemons/scripts/evpPD2P.py +144 -0
  196. pandaserver/daemons/scripts/hs_scrapers.py +264 -0
  197. pandaserver/daemons/scripts/metric_collector.py +927 -0
  198. pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
  199. pandaserver/daemons/scripts/pilotStreaming.py +109 -0
  200. pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
  201. pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
  202. pandaserver/daemons/scripts/task_evaluator.py +405 -0
  203. pandaserver/daemons/scripts/tmpwatch.py +70 -0
  204. pandaserver/daemons/scripts/worker_synchronization.py +97 -0
  205. pandaserver/daemons/utils.py +753 -0
  206. pandaserver/dataservice/DataServiceUtils.py +323 -0
  207. pandaserver/dataservice/ErrorCode.py +25 -0
  208. pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
  209. pandaserver/dataservice/__init__.py +0 -0
  210. pandaserver/dataservice/activator.py +79 -0
  211. pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
  212. pandaserver/dataservice/adder_dummy_plugin.py +31 -0
  213. pandaserver/dataservice/adder_gen.py +954 -0
  214. pandaserver/dataservice/adder_plugin_base.py +27 -0
  215. pandaserver/dataservice/adder_result.py +75 -0
  216. pandaserver/dataservice/adder_simple_plugin.py +160 -0
  217. pandaserver/dataservice/closer.py +247 -0
  218. pandaserver/dataservice/closer_atlas_plugin.py +90 -0
  219. pandaserver/dataservice/ddm.py +1381 -0
  220. pandaserver/dataservice/ddm_handler.py +95 -0
  221. pandaserver/dataservice/dyn_data_distributer.py +824 -0
  222. pandaserver/dataservice/event_lookup_client_ei.py +77 -0
  223. pandaserver/dataservice/event_picker.py +268 -0
  224. pandaserver/dataservice/finisher.py +194 -0
  225. pandaserver/dataservice/setupper.py +225 -0
  226. pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
  227. pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
  228. pandaserver/dataservice/setupper_plugin_base.py +67 -0
  229. pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
  230. pandaserver/jobdispatcher/ErrorCode.py +19 -0
  231. pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
  232. pandaserver/jobdispatcher/Protocol.py +443 -0
  233. pandaserver/jobdispatcher/Watcher.py +186 -0
  234. pandaserver/jobdispatcher/__init__.py +0 -0
  235. pandaserver/pandamcp/__init__.py +0 -0
  236. pandaserver/pandamcp/mcp_main.py +56 -0
  237. pandaserver/pandamcp/mcp_test_client.py +95 -0
  238. pandaserver/pandamcp/mcp_utils.py +74 -0
  239. pandaserver/proxycache/__init__.py +0 -0
  240. pandaserver/proxycache/panda_proxy_cache.py +240 -0
  241. pandaserver/proxycache/token_cache.py +141 -0
  242. pandaserver/server/.gacl +6 -0
  243. pandaserver/server/panda.py +539 -0
  244. pandaserver/srvcore/CoreUtils.py +572 -0
  245. pandaserver/srvcore/MailUtils.py +106 -0
  246. pandaserver/srvcore/__init__.py +0 -0
  247. pandaserver/srvcore/allowed_methods.py +100 -0
  248. pandaserver/srvcore/oidc_utils.py +152 -0
  249. pandaserver/srvcore/panda_request.py +196 -0
  250. pandaserver/srvcore/srv_msg_utils.py +42 -0
  251. pandaserver/taskbuffer/ConBridge.py +513 -0
  252. pandaserver/taskbuffer/DBProxyPool.py +95 -0
  253. pandaserver/taskbuffer/DataCarousel.py +3053 -0
  254. pandaserver/taskbuffer/DatasetSpec.py +137 -0
  255. pandaserver/taskbuffer/DdmSpec.py +201 -0
  256. pandaserver/taskbuffer/ErrorCode.py +87 -0
  257. pandaserver/taskbuffer/EventServiceUtils.py +396 -0
  258. pandaserver/taskbuffer/FileSpec.py +290 -0
  259. pandaserver/taskbuffer/GlobalShares.py +244 -0
  260. pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
  261. pandaserver/taskbuffer/Initializer.py +82 -0
  262. pandaserver/taskbuffer/InputChunk.py +1080 -0
  263. pandaserver/taskbuffer/JediCacheSpec.py +57 -0
  264. pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
  265. pandaserver/taskbuffer/JediFileSpec.py +242 -0
  266. pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
  267. pandaserver/taskbuffer/JobSpec.py +1006 -0
  268. pandaserver/taskbuffer/JobUtils.py +232 -0
  269. pandaserver/taskbuffer/NucleusSpec.py +116 -0
  270. pandaserver/taskbuffer/OraDBProxy.py +174 -0
  271. pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
  272. pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
  273. pandaserver/taskbuffer/ParseJobXML.py +350 -0
  274. pandaserver/taskbuffer/PickleFileSpec.py +18 -0
  275. pandaserver/taskbuffer/PickleJobSpec.py +25 -0
  276. pandaserver/taskbuffer/PrioUtil.py +40 -0
  277. pandaserver/taskbuffer/ProcessGroups.py +111 -0
  278. pandaserver/taskbuffer/ResourceSpec.py +175 -0
  279. pandaserver/taskbuffer/SQLDumper.py +37 -0
  280. pandaserver/taskbuffer/SQLManager.py +60 -0
  281. pandaserver/taskbuffer/SiteSpec.py +304 -0
  282. pandaserver/taskbuffer/SupErrors.py +10 -0
  283. pandaserver/taskbuffer/TaskBuffer.py +2882 -0
  284. pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
  285. pandaserver/taskbuffer/Utils.py +848 -0
  286. pandaserver/taskbuffer/WorkQueue.py +227 -0
  287. pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
  288. pandaserver/taskbuffer/WorkerSpec.py +143 -0
  289. pandaserver/taskbuffer/WrappedCursor.py +487 -0
  290. pandaserver/taskbuffer/WrappedPickle.py +88 -0
  291. pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
  292. pandaserver/taskbuffer/__init__.py +0 -0
  293. pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
  294. pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
  295. pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
  296. pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
  297. pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
  298. pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
  299. pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
  300. pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
  301. pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
  302. pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
  303. pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
  304. pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
  305. pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
  306. pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
  307. pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
  308. pandaserver/taskbuffer/retryModule.py +552 -0
  309. pandaserver/taskbuffer/task_split_rules.py +308 -0
  310. pandaserver/taskbuffer/workflow_processor.py +249 -0
  311. pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
  312. pandaserver/test/SchemaChecker.py +40 -0
  313. pandaserver/test/__init__.py +0 -0
  314. pandaserver/test/banUser.py +43 -0
  315. pandaserver/test/boostPrio.py +19 -0
  316. pandaserver/test/boostUser.py +35 -0
  317. pandaserver/test/callbackDDM.py +11 -0
  318. pandaserver/test/daod_on_demand.py +95 -0
  319. pandaserver/test/finishJob.py +184 -0
  320. pandaserver/test/finishTaskJEDI.py +11 -0
  321. pandaserver/test/getJobs.py +53 -0
  322. pandaserver/test/killJob.py +79 -0
  323. pandaserver/test/killJobLowPrio.py +99 -0
  324. pandaserver/test/killJobsInTask.py +56 -0
  325. pandaserver/test/killTask.py +80 -0
  326. pandaserver/test/killTaskJEDI.py +11 -0
  327. pandaserver/test/killUser.py +83 -0
  328. pandaserver/test/lsst/README.txt +7 -0
  329. pandaserver/test/lsst/lsstSubmit.py +139 -0
  330. pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
  331. pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
  332. pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
  333. pandaserver/test/reassignJobs.py +17 -0
  334. pandaserver/test/reassignSite.py +82 -0
  335. pandaserver/test/reassignTask.py +117 -0
  336. pandaserver/test/reassignWaiting.py +39 -0
  337. pandaserver/test/recoverLostFiles.py +7 -0
  338. pandaserver/test/reloadInputDS.py +51 -0
  339. pandaserver/test/sendCommandToJob.py +26 -0
  340. pandaserver/test/setDebugMode.py +38 -0
  341. pandaserver/test/setPriority.py +28 -0
  342. pandaserver/test/testEvgen.py +60 -0
  343. pandaserver/test/testEvgen17.py +58 -0
  344. pandaserver/test/testG4sim.py +81 -0
  345. pandaserver/test/testG4sim17.py +90 -0
  346. pandaserver/test/testGlobalShares.py +131 -0
  347. pandaserver/test/testReco.py +103 -0
  348. pandaserver/test/testSimulReco14.py +98 -0
  349. pandaserver/test/testSiteMap.py +26 -0
  350. pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
  351. pandaserver/userinterface/Client.py +1686 -0
  352. pandaserver/userinterface/UserIF.py +1799 -0
  353. pandaserver/userinterface/__init__.py +0 -0
  354. pandaserver/workflow/__init__.py +0 -0
  355. pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
  356. pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
  357. pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
  358. pandaserver/workflow/pcwl_test.py +122 -0
  359. pandaserver/workflow/pcwl_utils.py +384 -0
  360. pandaserver/workflow/psnakemake_container.json +68 -0
  361. pandaserver/workflow/psnakemake_task.json +62 -0
  362. pandaserver/workflow/psnakemake_test.py +60 -0
  363. pandaserver/workflow/snakeparser/__init__.py +3 -0
  364. pandaserver/workflow/snakeparser/extensions.py +74 -0
  365. pandaserver/workflow/snakeparser/log.py +53 -0
  366. pandaserver/workflow/snakeparser/names.py +3 -0
  367. pandaserver/workflow/snakeparser/parser.py +332 -0
  368. pandaserver/workflow/snakeparser/utils.py +29 -0
  369. pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
  370. pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
  371. pandaserver/workflow/workflow_base.py +531 -0
  372. pandaserver/workflow/workflow_core.py +1974 -0
  373. pandaserver/workflow/workflow_core_smoketest.py +388 -0
  374. pandaserver/workflow/workflow_parser.py +222 -0
  375. pandaserver/workflow/workflow_utils.py +1047 -0
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env python
2
+
3
+ import sys
4
+ import xml.dom.minidom
5
+ from urllib.parse import quote
6
+
7
+
8
+ class dom_job:
9
+ """infiles[inds]=[file1,file2...]
10
+ outfiles = [file1,file2...]
11
+ command - script that will be executed on the grid
12
+ prepend - list of (option,value) prepended to output file name
13
+ forward - list of (option,value) forwarded to the grid job
14
+ """
15
+
16
+ def __init__(s, domjob=None, primaryds=None, defaultcmd=None, defaultout=[]):
17
+ """Loads <job></job> from xml file.
18
+ If primaryds is set, makes sure it is present in job spec"""
19
+ s.infiles = {}
20
+ s.outfiles = []
21
+ s.command = defaultcmd
22
+ s.prepend = []
23
+ s.forward = []
24
+ if not domjob:
25
+ return
26
+ # script executed on the grid node for this job
27
+ if len(domjob.getElementsByTagName("command")) > 0:
28
+ s.command = dom_parser.text(domjob.getElementsByTagName("command")[0])
29
+ # input files
30
+ for inds in domjob.getElementsByTagName("inds"):
31
+ name = dom_parser.text(inds.getElementsByTagName("name")[0])
32
+ files = inds.getElementsByTagName("file")
33
+ if len(files) == 0:
34
+ continue
35
+ s.infiles[name] = []
36
+ for file in files:
37
+ s.infiles[name].append(dom_parser.text(file))
38
+ if primaryds and primaryds not in s.infiles.keys():
39
+ print(f"ERROR: primaryds={primaryds} must be present in each job")
40
+ sys.exit(0)
41
+ # output files (also, drop duplicates within this job)
42
+ outfiles = set(defaultout)
43
+ [outfiles.add(dom_parser.text(v)) for v in domjob.getElementsByTagName("output")]
44
+ s.outfiles = list(outfiles)
45
+ # gearing options
46
+ for o in domjob.getElementsByTagName("option"):
47
+ name = o.attributes["name"].value
48
+ value = dom_parser.text(o)
49
+ prepend = dom_parser.true(o.attributes["prepend"].value)
50
+ forward = dom_parser.true(o.attributes["forward"].value)
51
+ if prepend:
52
+ s.prepend.append((name, value))
53
+ if forward:
54
+ s.forward.append((name, value))
55
+
56
+ def to_dom(s):
57
+ """Converts this job to a dom tree branch"""
58
+ x = xml.dom.minidom.Document()
59
+ job = x.createElement("job")
60
+ for inds in s.infiles.keys():
61
+ job.appendChild(x.createElement("inds"))
62
+ job.childNodes[-1].appendChild(x.createElement("name"))
63
+ job.childNodes[-1].childNodes[-1].appendChild(x.createTextNode(inds))
64
+ for file in s.infiles[inds]:
65
+ job.childNodes[-1].appendChild(x.createElement("file"))
66
+ job.childNodes[-1].childNodes[-1].appendChild(x.createTextNode(file))
67
+ for outfile in s.outfiles:
68
+ job.appendChild(x.createElement("output"))
69
+ job.childNodes[-1].appendChild(x.createTextNode(outfile))
70
+ if s.command:
71
+ job.appendChild(x.createElement("command"))
72
+ job.childNodes[-1].appendChild(x.createTextNode(s.command))
73
+ for option in s.prepend + list(set(s.prepend + s.forward) - set(s.prepend)):
74
+ job.appendChild(x.createElement("option"))
75
+ job.childNodes[-1].setAttribute("name", str(option[0]))
76
+ if option in s.forward:
77
+ job.childNodes[-1].setAttribute("forward", "true")
78
+ else:
79
+ job.childNodes[-1].setAttribute("forward", "false")
80
+ if option in s.prepend:
81
+ job.childNodes[-1].setAttribute("prepend", "true")
82
+ else:
83
+ job.childNodes[-1].setAttribute("prepend", "false")
84
+ job.childNodes[-1].appendChild(x.createTextNode(str(option[1])))
85
+ return job
86
+
87
+ def files_in_DS(s, DS):
88
+ """Returns a list of files used in a given job in a given dataset"""
89
+ if DS in s.infiles:
90
+ return s.infiles[DS]
91
+ else:
92
+ return []
93
+
94
+ def forward_opts(s):
95
+ """passable string of forward options"""
96
+ return " ".join([f"{v[0]}={v[1]}" for v in s.forward])
97
+
98
+ def prepend_string(s):
99
+ """a tag string prepended to output files"""
100
+ return "_".join([f"{v[0]}{v[1]}" for v in s.prepend])
101
+
102
+ def exec_string(s):
103
+ """exec string for prun.
104
+ If user requested to run script run.sh (via <command>run.sh</command>), it will return
105
+ opt1=value1 opt2=value2 opt3=value3 run.sh
106
+ This way, all options will be set inside run.sh
107
+ """
108
+ return f"{s.forward_opts()} {s.command}"
109
+
110
+ def exec_string_enc(s):
111
+ """exec string for prun.
112
+ If user requested to run script run.sh (via <command>run.sh</command>), it will return
113
+ opt1=value1 opt2=value2 opt3=value3 run.sh
114
+ This way, all options will be set inside run.sh
115
+ """
116
+ comStr = f"{s.forward_opts()} {s.command}"
117
+ return quote(comStr)
118
+
119
+ def get_outmap_str(s, outMap):
120
+ """return mapping of original and new filenames"""
121
+ newMap = {}
122
+ for oldLFN, fileSpec in outMap.items():
123
+ newMap[oldLFN] = str(fileSpec.lfn)
124
+ return str(newMap)
125
+
126
+ def outputs_list(s, prepend=False):
127
+ """python list with finalized output file names"""
128
+ if prepend and s.prepend_string():
129
+ return [s.prepend_string() + "." + o for o in s.outfiles]
130
+ else:
131
+ return [o for o in s.outfiles]
132
+
133
+ def outputs(s, prepend=False):
134
+ """Comma-separated list of output files accepted by prun"""
135
+ return ",".join(s.outputs_list(prepend))
136
+
137
+
138
+ class dom_parser:
139
+ def __init__(s, fname=None, xmlStr=None):
140
+ """creates a dom object out of a text file (if provided)"""
141
+ s.fname = fname
142
+ s.dom = None
143
+ s.title = None
144
+ s.tag = None
145
+ s.command = None
146
+ s.outds = None
147
+ s.inds = {}
148
+ s.global_outfiles = []
149
+ s.jobs = []
150
+ s.primaryds = None
151
+ if fname:
152
+ s.dom = xml.dom.minidom.parse(fname)
153
+ s.parse()
154
+ s.check()
155
+ if xmlStr is not None:
156
+ s.dom = xml.dom.minidom.parseString(xmlStr)
157
+ s.parse()
158
+ s.check()
159
+
160
+ @staticmethod
161
+ def true(v):
162
+ """define True"""
163
+ return v in ("1", "true", "True", "TRUE", "yes", "Yes", "YES")
164
+
165
+ @staticmethod
166
+ def text(pnode):
167
+ """extracts the value stored in the node"""
168
+ rc = []
169
+ for node in pnode.childNodes:
170
+ if node.nodeType == node.TEXT_NODE:
171
+ rc.append(str(node.data).strip())
172
+ return "".join(rc)
173
+
174
+ def parse(s):
175
+ """loads submission configuration from an xml file"""
176
+ try:
177
+ # general settings
178
+ if len(s.dom.getElementsByTagName("title")) > 0:
179
+ s.title = dom_parser.text(s.dom.getElementsByTagName("title")[0])
180
+ else:
181
+ s.title = "Default title"
182
+ if len(s.dom.getElementsByTagName("tag")) > 0:
183
+ s.tag = dom_parser.text(s.dom.getElementsByTagName("tag")[0])
184
+ else:
185
+ s.tag = "default_tag"
186
+ s.command = None # can be overridden in subjobs
187
+ for elm in s.dom.getElementsByTagName("submission")[0].childNodes:
188
+ if elm.nodeName != "command":
189
+ continue
190
+ s.command = dom_parser.text(elm)
191
+ break
192
+ s.global_outfiles = [] # subjobs can append *additional* outputs
193
+ for elm in s.dom.getElementsByTagName("submission")[0].childNodes:
194
+ if elm.nodeName != "output":
195
+ continue
196
+ s.global_outfiles.append(dom_parser.text(elm))
197
+ s.outds = dom_parser.text(s.dom.getElementsByTagName("outds")[0])
198
+ # declaration of all input datasets
199
+ primarydss = []
200
+ for elm in s.dom.getElementsByTagName("submission")[0].childNodes:
201
+ if elm.nodeName != "inds":
202
+ continue
203
+ if "primary" in elm.attributes.keys() and dom_parser.true(elm.attributes["primary"].value):
204
+ primary = True
205
+ else:
206
+ primary = False
207
+ stream = dom_parser.text(elm.getElementsByTagName("stream")[0])
208
+ name = dom_parser.text(elm.getElementsByTagName("name")[0])
209
+ s.inds[name] = stream
210
+ if primary:
211
+ primarydss.append(name)
212
+ # see if one of the input datasets was explicitly labeled as inDS
213
+ if len(primarydss) == 1:
214
+ s.primaryds = primarydss[0]
215
+ else:
216
+ s.primaryds = None
217
+ for job in s.dom.getElementsByTagName("job"):
218
+ s.jobs.append(dom_job(job, primaryds=s.primaryds, defaultcmd=s.command, defaultout=s.global_outfiles))
219
+ except Exception:
220
+ print("ERROR: failed to parse" + " " + s.fname)
221
+ raise
222
+
223
+ def to_dom(s):
224
+ """Converts this submission to a dom tree branch"""
225
+ x = xml.dom.minidom.Document()
226
+ submission = x.createElement("submission")
227
+ if s.title:
228
+ submission.appendChild(x.createElement("title"))
229
+ submission.childNodes[-1].appendChild(x.createTextNode(s.title))
230
+ if s.tag:
231
+ submission.appendChild(x.createElement("tag"))
232
+ submission.childNodes[-1].appendChild(x.createTextNode(s.tag))
233
+ for name, stream in s.inds.items():
234
+ submission.appendChild(x.createElement("inds"))
235
+ if name == s.primaryds:
236
+ submission.childNodes[-1].setAttribute("primary", "true")
237
+ else:
238
+ submission.childNodes[-1].setAttribute("primary", "false")
239
+ submission.childNodes[-1].appendChild(x.createElement("stream"))
240
+ submission.childNodes[-1].childNodes[-1].appendChild(x.createTextNode(stream))
241
+ submission.childNodes[-1].appendChild(x.createElement("name"))
242
+ submission.childNodes[-1].childNodes[-1].appendChild(x.createTextNode(name))
243
+ if s.command:
244
+ submission.appendChild(x.createElement("command"))
245
+ submission.childNodes[-1].appendChild(x.createTextNode(s.command))
246
+ for outfile in s.global_outfiles:
247
+ submission.appendChild(x.createElement("output"))
248
+ submission.childNodes[-1].appendChild(x.createTextNode(outfile))
249
+ submission.appendChild(x.createElement("outds"))
250
+ submission.childNodes[-1].appendChild(x.createTextNode(s.outds))
251
+ for job in s.jobs:
252
+ submission.appendChild(job.to_dom())
253
+ return submission
254
+
255
+ def check(s):
256
+ """checks that all output files have unique qualifiers"""
257
+ quals = []
258
+ for j in s.jobs:
259
+ quals += j.outputs_list(True)
260
+ if len(list(set(quals))) != len(quals):
261
+ print("ERROR: found non-unique output file names across the jobs")
262
+ print("(you likely need to review xml options with prepend=true)")
263
+ sys.exit(0)
264
+
265
+ def input_datasets(s):
266
+ """returns a list of all used input datasets"""
267
+ DSs = set()
268
+ for j in s.jobs:
269
+ for ds in j.infiles.keys():
270
+ DSs.add(ds)
271
+ return list(DSs)
272
+
273
+ def inDS(s):
274
+ """chooses a dataset we'll call inDS; others will become secondaryDS"""
275
+ # user manually labeled one of datasets as primary, so make it inDS:
276
+ if s.primaryds:
277
+ return s.primaryds
278
+ # OR: choose inDS dataset randomly
279
+ else:
280
+ return s.input_datasets()[0]
281
+
282
+ def secondaryDSs(s):
283
+ """returns all secondaryDSs. This excludes inDS, unless inDS is managed by prun"""
284
+ return [d for d in s.input_datasets() if d != s.inDS()]
285
+
286
+ def writeInputToTxt(s):
287
+ """Prepares prun option --writeInputToTxt
288
+ comma-separated list of STREAM:STREAM.files.dat
289
+ """
290
+ out = []
291
+ DSs = s.secondaryDSs()
292
+ for i, DS in enumerate(DSs):
293
+ if DS in s.inds:
294
+ stream = s.inds[DS]
295
+ else:
296
+ stream = "IN%d" % (i + 1,)
297
+ out.append(f"{stream}:{stream}.files.dat")
298
+ out.append("IN:IN.files.dat")
299
+ return ",".join(out)
300
+
301
+ def files_in_DS(s, DS, regex=False):
302
+ """Returns a list of all files from a given dataset
303
+ that will be used in at least one job in this submission
304
+ If regex==True, the list is converted to a regex string
305
+ """
306
+ assert DS in s.input_datasets(), f"ERROR: dataset {DS} was not requested in the xml file"
307
+ files = []
308
+ for j in s.jobs:
309
+ if DS in j.infiles.keys():
310
+ files += j.infiles[DS]
311
+ if regex:
312
+ return "|".join(sorted(list(set(files))))
313
+ else:
314
+ return sorted(list(set(files)))
315
+
316
+ def nJobs(s):
317
+ return len(s.jobs)
318
+
319
+ def dump(s, verbose=True):
320
+ """prints a summary of this submission"""
321
+
322
+ def P(key, value=""):
323
+ if value == "":
324
+ print(key)
325
+ else:
326
+ print((key + ":").ljust(14) + " " + value)
327
+
328
+ P("XML FILE LOADED", s.fname)
329
+ P("Title", s.title)
330
+ P("Command", s.command)
331
+ P("InDS", s.inDS())
332
+ P("Output DS", s.outds)
333
+ P("njobs", s.nJobs())
334
+ if verbose:
335
+ for i, job in enumerate(s.jobs):
336
+ P("===============> JOB%d" % i)
337
+ P("command", job.exec_string())
338
+ P("outfiles", job.outputs())
339
+ P("INPUTS:")
340
+ j = 0
341
+ for dsname, files in job.infiles.items():
342
+ P(" Dataset%d" % j, dsname)
343
+ for k, fname in enumerate(files):
344
+ P(" File%d" % k, fname)
345
+ j += 1
346
+
347
+
348
+ if __name__ == "__main__":
349
+ p = dom_parser("./job.xml")
350
+ p.dump()
@@ -0,0 +1,18 @@
1
+ # Derived FileSpec to preserve changed attributes in pickle as it is impossible to change FileSpec.reserveChangedState
2
+ # consistently with all clients at the same time
3
+
4
+ from pandaserver.taskbuffer.FileSpec import FileSpec
5
+
6
+
7
+ class PickleFileSpec(FileSpec):
8
+ def __init__(self):
9
+ FileSpec.__init__(self)
10
+ object.__setattr__(self, "_reserveChangedState", True)
11
+
12
+ def update(self, spec):
13
+ spec._reserveChangedState = True
14
+ self.__setstate__(spec.__getstate__())
15
+
16
+ def __setstate__(self, state):
17
+ object.__setattr__(self, "_reserveChangedState", True)
18
+ FileSpec.__setstate__(self, state)
@@ -0,0 +1,25 @@
1
+ # Derived JobSpec to preserve changed attributes in pickle as it is impossible to change JobSpec.reserveChangedState
2
+ # consistently with all clients at the same time
3
+
4
+ from pandaserver.taskbuffer.JobSpec import JobSpec
5
+ from pandaserver.taskbuffer.PickleFileSpec import PickleFileSpec
6
+
7
+
8
+ class PickleJobSpec(JobSpec):
9
+ def __init__(self):
10
+ JobSpec.__init__(self)
11
+ object.__setattr__(self, "_reserveChangedState", True)
12
+
13
+ def update(self, spec):
14
+ spec._reserveChangedState = True
15
+ self.__setstate__(spec.__getstate__())
16
+ p_file_list = []
17
+ for file in self.Files:
18
+ p_file = PickleFileSpec()
19
+ p_file.update(file)
20
+ p_file_list.append(p_file)
21
+ object.__setattr__(self, "Files", p_file_list)
22
+
23
+ def __setstate__(self, state):
24
+ object.__setattr__(self, "_reserveChangedState", True)
25
+ JobSpec.__setstate__(self, state)
@@ -0,0 +1,40 @@
1
+ import json
2
+ import sys
3
+
4
+ # special permission
5
+ PERMISSION_KEY = "k"
6
+ PERMISSION_PROXY = "p"
7
+ PERMISSION_TOKEN_KEY = "t"
8
+ PERMISSION_SUPER_USER = "s"
9
+ PERMISSION_SUPER_GROUP = "g"
10
+
11
+
12
+ # convert UTF-8 to ASCII in json dumps
13
+ # This is needed for Python 2, but not for Python 3
14
+ def unicodeConvert(input):
15
+ if isinstance(input, dict):
16
+ retMap = {}
17
+ for tmpKey in input:
18
+ tmpVal = input[tmpKey]
19
+ retMap[unicodeConvert(tmpKey)] = unicodeConvert(tmpVal)
20
+ return retMap
21
+ elif isinstance(input, list):
22
+ retList = []
23
+ for tmpItem in input:
24
+ retList.append(unicodeConvert(tmpItem))
25
+ return retList
26
+ elif isinstance(input, str) and sys.version_info[0] == 2:
27
+ return input.encode("utf-8")
28
+ return input
29
+
30
+
31
+ # decode
32
+ # This is needed for Python 2, but not for Python 3
33
+ def decodeJSON(inputStr):
34
+ return json.loads(inputStr, object_hook=unicodeConvert)
35
+
36
+
37
+ # calculate priority for user jobs
38
+ def calculatePriority(priorityOffset, serNum, weight):
39
+ priority = int(1000 + priorityOffset - (serNum / 5) - int(100 * weight))
40
+ return priority
@@ -0,0 +1,111 @@
1
+ from pandaserver.taskbuffer import JobUtils
2
+
3
+ processGroups = [
4
+ ("others", []),
5
+ ("evgen", ["evgen"]),
6
+ ("simul", ["simul"]),
7
+ ("reprocessing", ["reprocessing"]),
8
+ ("test", ["prod_test", "validation"] + JobUtils.list_ptest_prod_sources),
9
+ ("mcore", ["mcore"]),
10
+ ("group", ["group"]),
11
+ ("deriv", ["deriv"]),
12
+ ("pile", ["pile"]),
13
+ ("merge", ["merge"]),
14
+ ]
15
+
16
+ # ('evgensimul', ['evgen','simul']),
17
+
18
+ # maximum number of debug jobs per user
19
+ maxDebugJobs = 3
20
+
21
+ # maximum number of debug jobs for prod role
22
+ maxDebugProdJobs = 30
23
+
24
+ # maximum number of debug jobs for working group
25
+ maxDebugWgJobs = 10
26
+
27
+ # extension level for GP
28
+ extensionLevel_1 = 1
29
+
30
+
31
+ # get corresponding group
32
+ def getProcessGroup(valGroup):
33
+ tmpGroup = None
34
+ for tmpKey, tmpList in processGroups:
35
+ # set default
36
+ if tmpGroup is None:
37
+ tmpGroup = tmpKey
38
+ continue
39
+ if valGroup in tmpList:
40
+ tmpGroup = tmpKey
41
+ break
42
+ # return
43
+ return tmpGroup
44
+
45
+
46
+ # convert cloud and processingType for extended PG
47
+ def converCPTforEPG(cloud, processingType, coreCount, workingGroup=None):
48
+ if coreCount in [0, 1, None]:
49
+ # use group queue for GP jobs
50
+ if workingGroup is not None and workingGroup.startswith("GP_"):
51
+ return cloud, "group"
52
+ return cloud, processingType
53
+ else:
54
+ # use MCORE queue for MPC jobs in all clouds
55
+ return cloud, "mcore"
56
+
57
+
58
+ # count the number of jobs per group
59
+ def countJobsPerGroup(valMap):
60
+ ret = {}
61
+ # loop over all clouds
62
+ for cloud in valMap:
63
+ cloudVal = valMap[cloud]
64
+ # add cloud
65
+ ret.setdefault(cloud, {})
66
+ # loop over all sites
67
+ for site in cloudVal:
68
+ siteVal = cloudVal[site]
69
+ # add site
70
+ ret[cloud].setdefault(site, {})
71
+ # loop over all types
72
+ for pType in siteVal:
73
+ typeVal = siteVal[pType]
74
+ # get process group
75
+ tmpGroup = getProcessGroup(pType)
76
+ # add group
77
+ ret[cloud][site].setdefault(tmpGroup, {})
78
+ # loop over all status
79
+ for jobStatus in typeVal:
80
+ statVal = typeVal[jobStatus]
81
+ ret[cloud][site][tmpGroup].setdefault(jobStatus, 0)
82
+ # add
83
+ ret[cloud][site][tmpGroup][jobStatus] += statVal
84
+ # return
85
+ return ret
86
+
87
+
88
+ # count the number of jobs per group for analysis
89
+ def countJobsPerGroupForAnal(valMap):
90
+ ret = {}
91
+ # loop over all sites
92
+ for site in valMap:
93
+ siteVal = valMap[site]
94
+ # add site
95
+ ret.setdefault(site, {})
96
+ # loop over all types
97
+ for pType in siteVal:
98
+ typeVal = siteVal[pType]
99
+ # get process group
100
+ tmpGroup = getProcessGroup(pType)
101
+ # add group
102
+ if tmpGroup not in ret[site]:
103
+ ret[site][tmpGroup] = {}
104
+ # loop over all status
105
+ for jobStatus in typeVal:
106
+ statVal = typeVal[jobStatus]
107
+ ret[site][tmpGroup].setdefault(jobStatus, 0)
108
+ # add
109
+ ret[site][tmpGroup][jobStatus] += statVal
110
+ # return
111
+ return ret