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,396 @@
1
+ import re
2
+
3
+ from pandaserver.taskbuffer.JobSpec import JobSpec
4
+
5
+ # status codes for each event range
6
+ ST_ready = 0
7
+ ST_sent = 1
8
+ ST_running = 2
9
+ ST_finished = 3
10
+ ST_cancelled = 4
11
+ ST_discarded = 5
12
+ ST_done = 6
13
+ ST_failed = 7
14
+ ST_fatal = 8
15
+ ST_merged = 9
16
+ ST_corrupted = 10
17
+ ST_reserved_fail = 98
18
+ ST_reserved_get = 99
19
+
20
+ ES_status_map = {
21
+ ST_ready: "ready",
22
+ ST_sent: "sent",
23
+ ST_running: "running",
24
+ ST_finished: "finished",
25
+ ST_cancelled: "cancelled",
26
+ ST_discarded: "discarded",
27
+ ST_done: "done",
28
+ ST_failed: "failed",
29
+ ST_fatal: "fatal",
30
+ ST_merged: "merged",
31
+ ST_corrupted: "corrupted",
32
+ ST_reserved_fail: "reserved_fail",
33
+ ST_reserved_get: "reserved_get",
34
+ }
35
+
36
+ # identifiers for specialHandling
37
+ esHeader = "es:"
38
+ singleConsumerType = {"runonce": "1", "storeonce": "2"}
39
+
40
+ # tags for special handling. check JobSpec._tagForSH for duplication
41
+ esToken = "eventservice"
42
+ esMergeToken = "esmerge"
43
+ dynamicNumEventsToken = JobSpec._tagForSH["dynamicNumEvents"]
44
+ mergeAtOsToken = JobSpec._tagForSH["mergeAtOs"]
45
+ resurrectConsumersToken = JobSpec._tagForSH["resurrectConsumers"]
46
+ singleToken = JobSpec._tagForSH["jobCloning"]
47
+
48
+
49
+ # values for job.eventService
50
+ esJobFlagNumber = 1
51
+ esMergeJobFlagNumber = 2
52
+ jobCloningFlagNumber = 3
53
+ jumboJobFlagNumber = 4
54
+ coJumboJobFlagNumber = 5
55
+ fineGrainedFlagNumber = 6
56
+
57
+
58
+ # values for task.eventService
59
+ TASK_NORMAL = 0
60
+ TASK_EVENT_SERVICE = 1
61
+ TASK_JOB_CLONING = 2
62
+ TASK_FINE_GRAINED = 3
63
+
64
+ # values for event.is_jumbo
65
+ eventTableIsJumbo = 1
66
+
67
+
68
+ # siteid for waiting co-jumbo jobs
69
+ siteIdForWaitingCoJumboJobs = "WAITING_CO_JUMBO"
70
+
71
+
72
+ # relation type for jobsets
73
+ relationTypeJS_ID = "jobset_id"
74
+ relationTypeJS_Retry = "jobset_retry"
75
+ relationTypeJS_Map = "jobset_map"
76
+ relationTypesForJS = [relationTypeJS_ID, relationTypeJS_Retry, relationTypeJS_Map]
77
+
78
+
79
+ # suffix for ES dataset and files to register to DDM
80
+ esSuffixDDM = ".events"
81
+ esScopeDDM = "transient"
82
+ esRegStatus = "esregister"
83
+
84
+ # default max number of ES job attempt
85
+ defMaxAttemptEsJob = 3
86
+
87
+
88
+ # pilot error code
89
+ PEC_corruptedInputFiles = [1171, 1145, 1175, 1103]
90
+ PEC_corruptedInputFilesTmp = [1099]
91
+
92
+
93
+ # encode file info
94
+ def encodeFileInfo(
95
+ lfn,
96
+ startEvent,
97
+ endEvent,
98
+ nEventsPerWorker,
99
+ maxAttempt=None,
100
+ firstOffset=None,
101
+ firstEvent=None,
102
+ ):
103
+ if maxAttempt is None:
104
+ maxAttempt = 10
105
+ if firstOffset is None:
106
+ return f"{lfn}/{startEvent}/{endEvent}/{nEventsPerWorker}/{maxAttempt}^"
107
+ else:
108
+ try:
109
+ totalOffset = firstEvent - firstOffset
110
+ except Exception:
111
+ totalOffset = 0
112
+ return f"{lfn}/{startEvent}/{endEvent}/{nEventsPerWorker}/{maxAttempt}/{totalOffset}^"
113
+
114
+
115
+ # get header for specialHandling
116
+ def getHeaderForES(esIndex):
117
+ return f"{esHeader}{esIndex}:"
118
+
119
+
120
+ # decode file info
121
+ def decodeFileInfo(specialHandling):
122
+ eventServiceInfo = {}
123
+ newSpecialHandling = ""
124
+ esIndex = None
125
+ try:
126
+ for tmpItem in specialHandling.split(","):
127
+ if tmpItem.startswith(esHeader):
128
+ tmpItem = re.sub("^" + esHeader, "", tmpItem)
129
+ # look for event service index
130
+ tmpMatch = re.search("^(\d+):", tmpItem)
131
+ if tmpMatch is not None:
132
+ esIndex = tmpMatch.group(1)
133
+ tmpItem = re.sub("^(\d+):", "", tmpItem)
134
+ for esItem in tmpItem.split("^"):
135
+ if esItem == "":
136
+ continue
137
+ esItems = esItem.split("/")
138
+ maxAttempt = 10
139
+ esOffset = 0
140
+ if len(esItems) == 3:
141
+ esLFN, esEvents, esRange = esItems
142
+ esStartEvent = 0
143
+ elif len(esItems) == 5:
144
+ esLFN, esStartEvent, esEndEvent, esRange, maxAttempt = esItems
145
+ esEvents = int(esEndEvent) - int(esStartEvent) + 1
146
+ elif len(esItems) == 6:
147
+ (
148
+ esLFN,
149
+ esStartEvent,
150
+ esEndEvent,
151
+ esRange,
152
+ maxAttempt,
153
+ esOffset,
154
+ ) = esItems
155
+ esEvents = int(esEndEvent) - int(esStartEvent) + 1
156
+ else:
157
+ esLFN, esStartEvent, esEndEvent, esRange = esItems
158
+ esEvents = int(esEndEvent) - int(esStartEvent) + 1
159
+ eventServiceInfo[esLFN] = {
160
+ "nEvents": int(esEvents),
161
+ "startEvent": int(esStartEvent),
162
+ "nEventsPerRange": int(esRange),
163
+ "maxAttempt": int(maxAttempt),
164
+ "offset": int(esOffset),
165
+ }
166
+ newSpecialHandling += f"{esToken},"
167
+ else:
168
+ newSpecialHandling += f"{tmpItem},"
169
+ newSpecialHandling = newSpecialHandling[:-1]
170
+ except Exception:
171
+ newSpecialHandling = specialHandling
172
+ return eventServiceInfo, newSpecialHandling, esIndex
173
+
174
+
175
+ # check if event service job
176
+ def isEventServiceJob(job):
177
+ # fine-grained job
178
+ if is_fine_grained_job(job):
179
+ return False
180
+ return isEventServiceSH(job.specialHandling)
181
+
182
+
183
+ # check if event service merge job
184
+ def isEventServiceMerge(job):
185
+ return isEventServiceMergeSH(job.specialHandling)
186
+
187
+
188
+ # check if specialHandling for event service
189
+ def isEventServiceSH(specialHandling):
190
+ try:
191
+ if specialHandling is not None and esToken in specialHandling.split(","):
192
+ return True
193
+ except Exception:
194
+ pass
195
+ return False
196
+
197
+
198
+ # check if specialHandling for event service merge
199
+ def isEventServiceMergeSH(specialHandling):
200
+ try:
201
+ if specialHandling is not None and esMergeToken in specialHandling.split(","):
202
+ return True
203
+ except Exception:
204
+ pass
205
+ return False
206
+
207
+
208
+ # set event service merge
209
+ def setEventServiceMerge(job):
210
+ try:
211
+ # set ES flag
212
+ job.eventService = esMergeJobFlagNumber
213
+ # set gshare to express
214
+ job.gshare = "Express"
215
+ # set flag for merging
216
+ if job.specialHandling is None:
217
+ job.specialHandling = esMergeToken
218
+ else:
219
+ newSpecialHandling = ""
220
+ # remove flag for event service
221
+ for tmpFlag in job.specialHandling.split(","):
222
+ if tmpFlag in ["", esToken]:
223
+ continue
224
+ newSpecialHandling += tmpFlag
225
+ newSpecialHandling += ","
226
+ newSpecialHandling += esMergeToken
227
+ job.specialHandling = newSpecialHandling
228
+ # remove fake flag
229
+ job.removeFakeJobToIgnore()
230
+ except Exception:
231
+ pass
232
+
233
+
234
+ # check if specialHandling for job cloning
235
+ def isJobCloningSH(specialHandling):
236
+ try:
237
+ if specialHandling is not None:
238
+ for token in specialHandling.split(","):
239
+ if singleToken == token.split(":")[0]:
240
+ return True
241
+ except Exception:
242
+ pass
243
+ return False
244
+
245
+
246
+ # check if event service job
247
+ def isJobCloningJob(job):
248
+ return isJobCloningSH(job.specialHandling)
249
+
250
+
251
+ # set header for job cloning
252
+ def setHeaderForJobCloning(specialHandling, scType):
253
+ if specialHandling is None:
254
+ specialHandling = ""
255
+ tokens = specialHandling.split(",")
256
+ while True:
257
+ try:
258
+ tokens.remove("")
259
+ except Exception:
260
+ break
261
+ if scType in singleConsumerType.values():
262
+ tokens.append(f"{singleToken}:{scType}")
263
+ return ",".join(tokens)
264
+
265
+
266
+ # get consumer type
267
+ def getJobCloningType(job):
268
+ if job.specialHandling is not None:
269
+ for token in job.specialHandling.split(","):
270
+ if singleToken == token.split(":")[0]:
271
+ for tmpKey in singleConsumerType:
272
+ tmpVal = singleConsumerType[tmpKey]
273
+ if tmpVal == token.split(":")[-1]:
274
+ return tmpKey
275
+ return ""
276
+
277
+
278
+ # get consumer value
279
+ def getJobCloningValue(scType):
280
+ if scType in singleConsumerType:
281
+ return singleConsumerType[scType]
282
+ return ""
283
+
284
+
285
+ # set header for dynamic number of events
286
+ def setHeaderForDynNumEvents(specialHandling):
287
+ if specialHandling is None:
288
+ specialHandling = ""
289
+ tokens = specialHandling.split(",")
290
+ while True:
291
+ try:
292
+ tokens.remove("")
293
+ except Exception:
294
+ break
295
+ tokens.append(dynamicNumEventsToken)
296
+ return ",".join(tokens)
297
+
298
+
299
+ # check if specialHandling for dynamic number of events
300
+ def isDynNumEventsSH(specialHandling):
301
+ try:
302
+ if specialHandling is not None:
303
+ if dynamicNumEventsToken in specialHandling.split(","):
304
+ return True
305
+ except Exception:
306
+ pass
307
+ return False
308
+
309
+
310
+ # remove event service header
311
+ def removeHeaderForES(job):
312
+ if job.specialHandling is not None:
313
+ items = job.specialHandling.split(",")
314
+ newItems = []
315
+ for item in items:
316
+ if re.search("^" + esHeader + ".+", item) is None:
317
+ newItems.append(item)
318
+ job.specialHandling = ",".join(newItems)
319
+
320
+
321
+ # check if jumbo job
322
+ def isJumboJob(job):
323
+ return job.eventService == jumboJobFlagNumber
324
+
325
+
326
+ # check if cooperative with jumbo job
327
+ def isCoJumboJob(job):
328
+ return job.eventService == coJumboJobFlagNumber
329
+
330
+
331
+ # set header for merge at OS
332
+ def setHeaderForMergeAtOS(specialHandling):
333
+ if specialHandling is None:
334
+ specialHandling = ""
335
+ tokens = specialHandling.split(",")
336
+ while True:
337
+ try:
338
+ tokens.remove("")
339
+ except Exception:
340
+ break
341
+ if mergeAtOsToken not in tokens:
342
+ tokens.append(mergeAtOsToken)
343
+ return ",".join(tokens)
344
+
345
+
346
+ # check if specialHandling for merge at OS
347
+ def isMergeAtOS(specialHandling):
348
+ try:
349
+ if specialHandling is not None:
350
+ if mergeAtOsToken in specialHandling.split(","):
351
+ return True
352
+ except Exception:
353
+ pass
354
+ return False
355
+
356
+
357
+ # get dataset name for event service
358
+ def getEsDatasetName(taskID):
359
+ esDataset = f"{esScopeDDM}:{taskID}{esSuffixDDM}"
360
+ return esDataset
361
+
362
+
363
+ # set header to resurrect consumers
364
+ def setHeaderToResurrectConsumers(specialHandling):
365
+ if specialHandling is None:
366
+ specialHandling = ""
367
+ tokens = specialHandling.split(",")
368
+ while True:
369
+ try:
370
+ tokens.remove("")
371
+ except Exception:
372
+ break
373
+ if resurrectConsumersToken not in tokens:
374
+ tokens.append(resurrectConsumersToken)
375
+ return ",".join(tokens)
376
+
377
+
378
+ # check if specialHandling to resurrect consumers
379
+ def isResurrectConsumers(specialHandling):
380
+ try:
381
+ if specialHandling is not None:
382
+ if resurrectConsumersToken in specialHandling.split(","):
383
+ return True
384
+ except Exception:
385
+ pass
386
+ return False
387
+
388
+
389
+ # check if fine-grained job
390
+ def is_fine_grained_job(job):
391
+ return job.eventService == fineGrainedFlagNumber
392
+
393
+
394
+ # set fine-grained
395
+ def set_fine_grained(job):
396
+ job.eventService = fineGrainedFlagNumber
@@ -0,0 +1,290 @@
1
+ """
2
+ file specification
3
+
4
+ """
5
+
6
+ import datetime
7
+
8
+ reserveChangedState = False
9
+
10
+
11
+ class FileSpec(object):
12
+ # attributes
13
+ _attributes = (
14
+ "row_ID",
15
+ "PandaID",
16
+ "GUID",
17
+ "lfn",
18
+ "type",
19
+ "dataset",
20
+ "status",
21
+ "prodDBlock",
22
+ "prodDBlockToken",
23
+ "dispatchDBlock",
24
+ "dispatchDBlockToken",
25
+ "destinationDBlock",
26
+ "destinationDBlockToken",
27
+ "destinationSE",
28
+ "fsize",
29
+ "md5sum",
30
+ "checksum",
31
+ "scope",
32
+ "jediTaskID",
33
+ "datasetID",
34
+ "fileID",
35
+ "attemptNr",
36
+ )
37
+ # slots
38
+ __slots__ = _attributes + (
39
+ "_owner",
40
+ "_changedAttrs",
41
+ "_oldPandaID",
42
+ "_reserveChangedState",
43
+ )
44
+ # attributes which have 0 by default
45
+ _zeroAttrs = ("fsize",)
46
+ # mapping between sequence and attr
47
+ _seqAttrMap = {"row_ID": "ATLAS_PANDA.FILESTABLE4_ROW_ID_SEQ.nextval"}
48
+
49
+ # constructor
50
+ def __init__(self):
51
+ # install attributes
52
+ for attr in self._attributes:
53
+ object.__setattr__(self, attr, None)
54
+ # set owner to synchronize PandaID
55
+ object.__setattr__(self, "_owner", None)
56
+ # map of changed attributes
57
+ object.__setattr__(self, "_changedAttrs", {})
58
+ # old PandaID
59
+ object.__setattr__(self, "_oldPandaID", "NULL")
60
+ # reserve changed state at instance level
61
+ object.__setattr__(self, "_reserveChangedState", False)
62
+
63
+ # override __getattribute__ for SQL and PandaID
64
+ def __getattribute__(self, name):
65
+ # PandaID
66
+ if name == "PandaID":
67
+ if self._owner is None:
68
+ return "NULL"
69
+ return self._owner.PandaID
70
+ # others
71
+ ret = object.__getattribute__(self, name)
72
+ if ret is None:
73
+ return "NULL"
74
+ return ret
75
+
76
+ # override __setattr__ to collect the changed attributes
77
+ def __setattr__(self, name, value):
78
+ oldVal = getattr(self, name)
79
+ object.__setattr__(self, name, value)
80
+ newVal = getattr(self, name)
81
+ # collect changed attributes
82
+ if oldVal != newVal:
83
+ self._changedAttrs[name] = value
84
+
85
+ # set owner
86
+ def setOwner(self, owner):
87
+ self._owner = owner
88
+ self._oldPandaID = self.PandaID
89
+
90
+ # reset changed attribute list
91
+ def resetChangedList(self):
92
+ self._oldPandaID = self.PandaID
93
+ object.__setattr__(self, "_changedAttrs", {})
94
+
95
+ # return a tuple of values
96
+ def values(self):
97
+ ret = []
98
+ for attr in self._attributes:
99
+ val = getattr(self, attr)
100
+ ret.append(val)
101
+ return tuple(ret)
102
+
103
+ # return map of values
104
+ def valuesMap(self, useSeq=False, onlyChanged=False):
105
+ ret = {}
106
+ for attr in self._attributes:
107
+ if useSeq and attr in self._seqAttrMap:
108
+ continue
109
+ if onlyChanged:
110
+ if attr == "PandaID":
111
+ if self.PandaID == self._oldPandaID:
112
+ continue
113
+ elif attr not in self._changedAttrs:
114
+ continue
115
+ val = getattr(self, attr)
116
+ if val == "NULL":
117
+ if attr in self._zeroAttrs:
118
+ val = 0
119
+ else:
120
+ val = None
121
+ ret[f":{attr}"] = val
122
+ return ret
123
+
124
+ # pack tuple into FileSpec
125
+ def pack(self, values):
126
+ for i in range(len(self._attributes)):
127
+ attr = self._attributes[i]
128
+ val = values[i]
129
+ object.__setattr__(self, attr, val)
130
+
131
+ # return state values to be pickled
132
+ def __getstate__(self):
133
+ state = []
134
+ for attr in self._attributes:
135
+ val = getattr(self, attr)
136
+ state.append(val)
137
+ if reserveChangedState or self._reserveChangedState:
138
+ state.append(self._changedAttrs)
139
+ # append owner info
140
+ state.append(self._owner)
141
+ return state
142
+
143
+ # restore state from the unpickled state values
144
+ def __setstate__(self, state):
145
+ pandaID = "NULL"
146
+ for i in range(len(self._attributes)):
147
+ if i + 1 < len(state):
148
+ object.__setattr__(self, self._attributes[i], state[i])
149
+ else:
150
+ object.__setattr__(self, self._attributes[i], "NULL")
151
+ if self._attributes[i] == "PandaID":
152
+ pandaID = state[i]
153
+ object.__setattr__(self, "_owner", state[-1])
154
+ object.__setattr__(self, "_oldPandaID", pandaID)
155
+ if not hasattr(self, "_reserveChangedState"):
156
+ object.__setattr__(self, "_reserveChangedState", False)
157
+ if reserveChangedState or self._reserveChangedState:
158
+ object.__setattr__(self, "_changedAttrs", state[-2])
159
+ else:
160
+ object.__setattr__(self, "_changedAttrs", {})
161
+
162
+ # return column names for INSERT
163
+ def columnNames(cls, withMod=False):
164
+ ret = ""
165
+ for attr in cls._attributes:
166
+ if ret != "":
167
+ ret += ","
168
+ ret += attr
169
+ # add modificationTime
170
+ if withMod:
171
+ ret += ",modificationTime"
172
+ return ret
173
+
174
+ columnNames = classmethod(columnNames)
175
+
176
+ # return expression of values for INSERT
177
+ def valuesExpression(cls):
178
+ ret = "VALUES("
179
+ for attr in cls._attributes:
180
+ ret += "%s"
181
+ if attr != cls._attributes[len(cls._attributes) - 1]:
182
+ ret += ","
183
+ ret += ")"
184
+ return ret
185
+
186
+ valuesExpression = classmethod(valuesExpression)
187
+
188
+ # return expression of bind variables for INSERT
189
+ def bindValuesExpression(cls, useSeq=False, withMod=False):
190
+ from pandaserver.config import panda_config
191
+
192
+ ret = "VALUES("
193
+ for attr in cls._attributes:
194
+ if useSeq and attr in cls._seqAttrMap:
195
+ if panda_config.backend == "mysql":
196
+ # mysql
197
+ ret += f"NULL,"
198
+ else:
199
+ # oracle
200
+ ret += f"{cls._seqAttrMap[attr]},"
201
+ else:
202
+ ret += f":{attr},"
203
+ ret = ret[:-1]
204
+ # add modificationTime
205
+ if withMod:
206
+ ret += ",:modificationTime"
207
+ ret += ")"
208
+ return ret
209
+
210
+ bindValuesExpression = classmethod(bindValuesExpression)
211
+
212
+ # return an expression for UPDATE
213
+ def updateExpression(cls):
214
+ ret = ""
215
+ for attr in cls._attributes:
216
+ ret = ret + attr + "=%s"
217
+ if attr != cls._attributes[len(cls._attributes) - 1]:
218
+ ret += ","
219
+ return ret
220
+
221
+ updateExpression = classmethod(updateExpression)
222
+
223
+ # return an expression of bind variables for UPDATE
224
+ def bindUpdateExpression(cls):
225
+ ret = ""
226
+ for attr in cls._attributes:
227
+ ret += f"{attr}=:{attr},"
228
+ ret = ret[:-1]
229
+ ret += " "
230
+ return ret
231
+
232
+ bindUpdateExpression = classmethod(bindUpdateExpression)
233
+
234
+ # return an expression of bind variables for UPDATE to update only changed attributes
235
+ def bindUpdateChangesExpression(self):
236
+ ret = ""
237
+ for attr in self._attributes:
238
+ if attr in self._changedAttrs or (attr == "PandaID" and self.PandaID != self._oldPandaID):
239
+ ret += f"{attr}=:{attr},"
240
+ ret = ret[:-1]
241
+ ret += " "
242
+ return ret
243
+
244
+ # check if unmerged input
245
+ def isUnMergedInput(self):
246
+ if self.type == "input" and self.dispatchDBlockToken == "TOMERGE":
247
+ return True
248
+ return False
249
+
250
+ # check if unmerged output
251
+ def isUnMergedOutput(self):
252
+ if self.type in ["output", "log"] and self.destinationDBlockToken == "TOMERGE":
253
+ return True
254
+ return False
255
+
256
+ # allow no output
257
+ def allowNoOutput(self):
258
+ if self.dispatchDBlockToken in ["NULL", None, ""]:
259
+ items = []
260
+ else:
261
+ items = self.dispatchDBlockToken.split(",")
262
+ if "an" not in items:
263
+ items.append("an")
264
+ self.dispatchDBlockToken = ",".join(items)
265
+
266
+ # check if no output is allowed
267
+ def isAllowedNoOutput(self):
268
+ try:
269
+ if "an" in self.dispatchDBlockToken.split(","):
270
+ return True
271
+ except Exception:
272
+ pass
273
+ return False
274
+
275
+ # dump to be json-serializable
276
+ def dump_to_json_serializable(self):
277
+ stat = self.__getstate__()[:-1]
278
+ # set None as _owner
279
+ stat.append(None)
280
+ return stat
281
+
282
+ # to a dictionary
283
+ def to_dict(self):
284
+ ret = {}
285
+ for a in self._attributes:
286
+ v = getattr(self, a)
287
+ if v == "NULL":
288
+ v = None
289
+ ret[a] = v
290
+ return ret