rucio 32.8.6__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.

Potentially problematic release.


This version of rucio might be problematic. Click here for more details.

Files changed (481) hide show
  1. rucio/__init__.py +18 -0
  2. rucio/alembicrevision.py +16 -0
  3. rucio/api/__init__.py +14 -0
  4. rucio/api/account.py +266 -0
  5. rucio/api/account_limit.py +287 -0
  6. rucio/api/authentication.py +302 -0
  7. rucio/api/config.py +218 -0
  8. rucio/api/credential.py +60 -0
  9. rucio/api/did.py +726 -0
  10. rucio/api/dirac.py +71 -0
  11. rucio/api/exporter.py +60 -0
  12. rucio/api/heartbeat.py +62 -0
  13. rucio/api/identity.py +160 -0
  14. rucio/api/importer.py +46 -0
  15. rucio/api/lifetime_exception.py +95 -0
  16. rucio/api/lock.py +131 -0
  17. rucio/api/meta.py +85 -0
  18. rucio/api/permission.py +72 -0
  19. rucio/api/quarantined_replica.py +69 -0
  20. rucio/api/replica.py +528 -0
  21. rucio/api/request.py +220 -0
  22. rucio/api/rse.py +601 -0
  23. rucio/api/rule.py +335 -0
  24. rucio/api/scope.py +89 -0
  25. rucio/api/subscription.py +255 -0
  26. rucio/api/temporary_did.py +49 -0
  27. rucio/api/vo.py +112 -0
  28. rucio/client/__init__.py +16 -0
  29. rucio/client/accountclient.py +413 -0
  30. rucio/client/accountlimitclient.py +155 -0
  31. rucio/client/baseclient.py +929 -0
  32. rucio/client/client.py +77 -0
  33. rucio/client/configclient.py +113 -0
  34. rucio/client/credentialclient.py +54 -0
  35. rucio/client/didclient.py +691 -0
  36. rucio/client/diracclient.py +48 -0
  37. rucio/client/downloadclient.py +1674 -0
  38. rucio/client/exportclient.py +44 -0
  39. rucio/client/fileclient.py +51 -0
  40. rucio/client/importclient.py +42 -0
  41. rucio/client/lifetimeclient.py +74 -0
  42. rucio/client/lockclient.py +99 -0
  43. rucio/client/metaclient.py +137 -0
  44. rucio/client/pingclient.py +45 -0
  45. rucio/client/replicaclient.py +444 -0
  46. rucio/client/requestclient.py +109 -0
  47. rucio/client/rseclient.py +664 -0
  48. rucio/client/ruleclient.py +287 -0
  49. rucio/client/scopeclient.py +88 -0
  50. rucio/client/subscriptionclient.py +161 -0
  51. rucio/client/touchclient.py +78 -0
  52. rucio/client/uploadclient.py +871 -0
  53. rucio/common/__init__.py +14 -0
  54. rucio/common/cache.py +74 -0
  55. rucio/common/config.py +796 -0
  56. rucio/common/constants.py +92 -0
  57. rucio/common/constraints.py +18 -0
  58. rucio/common/didtype.py +187 -0
  59. rucio/common/dumper/__init__.py +306 -0
  60. rucio/common/dumper/consistency.py +449 -0
  61. rucio/common/dumper/data_models.py +325 -0
  62. rucio/common/dumper/path_parsing.py +65 -0
  63. rucio/common/exception.py +1092 -0
  64. rucio/common/extra.py +37 -0
  65. rucio/common/logging.py +404 -0
  66. rucio/common/pcache.py +1387 -0
  67. rucio/common/policy.py +84 -0
  68. rucio/common/schema/__init__.py +143 -0
  69. rucio/common/schema/atlas.py +411 -0
  70. rucio/common/schema/belleii.py +406 -0
  71. rucio/common/schema/cms.py +478 -0
  72. rucio/common/schema/domatpc.py +399 -0
  73. rucio/common/schema/escape.py +424 -0
  74. rucio/common/schema/generic.py +431 -0
  75. rucio/common/schema/generic_multi_vo.py +410 -0
  76. rucio/common/schema/icecube.py +404 -0
  77. rucio/common/schema/lsst.py +423 -0
  78. rucio/common/stomp_utils.py +160 -0
  79. rucio/common/stopwatch.py +56 -0
  80. rucio/common/test_rucio_server.py +148 -0
  81. rucio/common/types.py +158 -0
  82. rucio/common/utils.py +1946 -0
  83. rucio/core/__init__.py +14 -0
  84. rucio/core/account.py +426 -0
  85. rucio/core/account_counter.py +171 -0
  86. rucio/core/account_limit.py +357 -0
  87. rucio/core/authentication.py +563 -0
  88. rucio/core/config.py +386 -0
  89. rucio/core/credential.py +218 -0
  90. rucio/core/did.py +3102 -0
  91. rucio/core/did_meta_plugins/__init__.py +250 -0
  92. rucio/core/did_meta_plugins/did_column_meta.py +326 -0
  93. rucio/core/did_meta_plugins/did_meta_plugin_interface.py +116 -0
  94. rucio/core/did_meta_plugins/filter_engine.py +573 -0
  95. rucio/core/did_meta_plugins/json_meta.py +215 -0
  96. rucio/core/did_meta_plugins/mongo_meta.py +199 -0
  97. rucio/core/did_meta_plugins/postgres_meta.py +317 -0
  98. rucio/core/dirac.py +208 -0
  99. rucio/core/distance.py +164 -0
  100. rucio/core/exporter.py +59 -0
  101. rucio/core/heartbeat.py +263 -0
  102. rucio/core/identity.py +290 -0
  103. rucio/core/importer.py +248 -0
  104. rucio/core/lifetime_exception.py +377 -0
  105. rucio/core/lock.py +474 -0
  106. rucio/core/message.py +241 -0
  107. rucio/core/meta.py +190 -0
  108. rucio/core/monitor.py +441 -0
  109. rucio/core/naming_convention.py +154 -0
  110. rucio/core/nongrid_trace.py +124 -0
  111. rucio/core/oidc.py +1339 -0
  112. rucio/core/permission/__init__.py +107 -0
  113. rucio/core/permission/atlas.py +1333 -0
  114. rucio/core/permission/belleii.py +1076 -0
  115. rucio/core/permission/cms.py +1166 -0
  116. rucio/core/permission/escape.py +1076 -0
  117. rucio/core/permission/generic.py +1128 -0
  118. rucio/core/permission/generic_multi_vo.py +1148 -0
  119. rucio/core/quarantined_replica.py +190 -0
  120. rucio/core/replica.py +3627 -0
  121. rucio/core/replica_sorter.py +368 -0
  122. rucio/core/request.py +2241 -0
  123. rucio/core/rse.py +1835 -0
  124. rucio/core/rse_counter.py +155 -0
  125. rucio/core/rse_expression_parser.py +460 -0
  126. rucio/core/rse_selector.py +277 -0
  127. rucio/core/rule.py +3419 -0
  128. rucio/core/rule_grouping.py +1473 -0
  129. rucio/core/scope.py +152 -0
  130. rucio/core/subscription.py +316 -0
  131. rucio/core/temporary_did.py +188 -0
  132. rucio/core/topology.py +448 -0
  133. rucio/core/trace.py +361 -0
  134. rucio/core/transfer.py +1233 -0
  135. rucio/core/vo.py +151 -0
  136. rucio/core/volatile_replica.py +123 -0
  137. rucio/daemons/__init__.py +14 -0
  138. rucio/daemons/abacus/__init__.py +14 -0
  139. rucio/daemons/abacus/account.py +106 -0
  140. rucio/daemons/abacus/collection_replica.py +113 -0
  141. rucio/daemons/abacus/rse.py +107 -0
  142. rucio/daemons/atropos/__init__.py +14 -0
  143. rucio/daemons/atropos/atropos.py +243 -0
  144. rucio/daemons/auditor/__init__.py +261 -0
  145. rucio/daemons/auditor/hdfs.py +86 -0
  146. rucio/daemons/auditor/srmdumps.py +284 -0
  147. rucio/daemons/automatix/__init__.py +14 -0
  148. rucio/daemons/automatix/automatix.py +281 -0
  149. rucio/daemons/badreplicas/__init__.py +14 -0
  150. rucio/daemons/badreplicas/minos.py +311 -0
  151. rucio/daemons/badreplicas/minos_temporary_expiration.py +173 -0
  152. rucio/daemons/badreplicas/necromancer.py +200 -0
  153. rucio/daemons/bb8/__init__.py +14 -0
  154. rucio/daemons/bb8/bb8.py +356 -0
  155. rucio/daemons/bb8/common.py +762 -0
  156. rucio/daemons/bb8/nuclei_background_rebalance.py +147 -0
  157. rucio/daemons/bb8/t2_background_rebalance.py +146 -0
  158. rucio/daemons/c3po/__init__.py +14 -0
  159. rucio/daemons/c3po/algorithms/__init__.py +14 -0
  160. rucio/daemons/c3po/algorithms/simple.py +131 -0
  161. rucio/daemons/c3po/algorithms/t2_free_space.py +125 -0
  162. rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +127 -0
  163. rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +279 -0
  164. rucio/daemons/c3po/c3po.py +342 -0
  165. rucio/daemons/c3po/collectors/__init__.py +14 -0
  166. rucio/daemons/c3po/collectors/agis.py +108 -0
  167. rucio/daemons/c3po/collectors/free_space.py +62 -0
  168. rucio/daemons/c3po/collectors/jedi_did.py +48 -0
  169. rucio/daemons/c3po/collectors/mock_did.py +46 -0
  170. rucio/daemons/c3po/collectors/network_metrics.py +63 -0
  171. rucio/daemons/c3po/collectors/workload.py +110 -0
  172. rucio/daemons/c3po/utils/__init__.py +14 -0
  173. rucio/daemons/c3po/utils/dataset_cache.py +40 -0
  174. rucio/daemons/c3po/utils/expiring_dataset_cache.py +45 -0
  175. rucio/daemons/c3po/utils/expiring_list.py +63 -0
  176. rucio/daemons/c3po/utils/popularity.py +82 -0
  177. rucio/daemons/c3po/utils/timeseries.py +76 -0
  178. rucio/daemons/cache/__init__.py +14 -0
  179. rucio/daemons/cache/consumer.py +191 -0
  180. rucio/daemons/common.py +391 -0
  181. rucio/daemons/conveyor/__init__.py +14 -0
  182. rucio/daemons/conveyor/common.py +530 -0
  183. rucio/daemons/conveyor/finisher.py +492 -0
  184. rucio/daemons/conveyor/poller.py +372 -0
  185. rucio/daemons/conveyor/preparer.py +198 -0
  186. rucio/daemons/conveyor/receiver.py +206 -0
  187. rucio/daemons/conveyor/stager.py +127 -0
  188. rucio/daemons/conveyor/submitter.py +379 -0
  189. rucio/daemons/conveyor/throttler.py +468 -0
  190. rucio/daemons/follower/__init__.py +14 -0
  191. rucio/daemons/follower/follower.py +97 -0
  192. rucio/daemons/hermes/__init__.py +14 -0
  193. rucio/daemons/hermes/hermes.py +738 -0
  194. rucio/daemons/judge/__init__.py +14 -0
  195. rucio/daemons/judge/cleaner.py +149 -0
  196. rucio/daemons/judge/evaluator.py +172 -0
  197. rucio/daemons/judge/injector.py +154 -0
  198. rucio/daemons/judge/repairer.py +144 -0
  199. rucio/daemons/oauthmanager/__init__.py +14 -0
  200. rucio/daemons/oauthmanager/oauthmanager.py +199 -0
  201. rucio/daemons/reaper/__init__.py +14 -0
  202. rucio/daemons/reaper/dark_reaper.py +272 -0
  203. rucio/daemons/reaper/light_reaper.py +255 -0
  204. rucio/daemons/reaper/reaper.py +701 -0
  205. rucio/daemons/replicarecoverer/__init__.py +14 -0
  206. rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +487 -0
  207. rucio/daemons/storage/__init__.py +14 -0
  208. rucio/daemons/storage/consistency/__init__.py +14 -0
  209. rucio/daemons/storage/consistency/actions.py +753 -0
  210. rucio/daemons/tracer/__init__.py +14 -0
  211. rucio/daemons/tracer/kronos.py +513 -0
  212. rucio/daemons/transmogrifier/__init__.py +14 -0
  213. rucio/daemons/transmogrifier/transmogrifier.py +753 -0
  214. rucio/daemons/undertaker/__init__.py +14 -0
  215. rucio/daemons/undertaker/undertaker.py +137 -0
  216. rucio/db/__init__.py +14 -0
  217. rucio/db/sqla/__init__.py +38 -0
  218. rucio/db/sqla/constants.py +192 -0
  219. rucio/db/sqla/migrate_repo/__init__.py +14 -0
  220. rucio/db/sqla/migrate_repo/env.py +111 -0
  221. rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +71 -0
  222. rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +50 -0
  223. rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +61 -0
  224. rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +46 -0
  225. rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +93 -0
  226. rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +78 -0
  227. rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +46 -0
  228. rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +53 -0
  229. rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +69 -0
  230. rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +42 -0
  231. rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +46 -0
  232. rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +61 -0
  233. rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +42 -0
  234. rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +141 -0
  235. rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +75 -0
  236. rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +75 -0
  237. rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +46 -0
  238. rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +51 -0
  239. rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +135 -0
  240. rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +65 -0
  241. rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +42 -0
  242. rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +66 -0
  243. rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +54 -0
  244. rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +43 -0
  245. rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +46 -0
  246. rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +47 -0
  247. rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +54 -0
  248. rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +39 -0
  249. rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +48 -0
  250. rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +47 -0
  251. rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +48 -0
  252. rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +59 -0
  253. rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +47 -0
  254. rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +72 -0
  255. rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +46 -0
  256. rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +45 -0
  257. rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +48 -0
  258. rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +48 -0
  259. rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +42 -0
  260. rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +69 -0
  261. rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +46 -0
  262. rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +78 -0
  263. rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +62 -0
  264. rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +74 -0
  265. rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +44 -0
  266. rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +67 -0
  267. rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +134 -0
  268. rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +58 -0
  269. rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +79 -0
  270. rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +61 -0
  271. rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +45 -0
  272. rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +46 -0
  273. rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +65 -0
  274. rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +42 -0
  275. rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +46 -0
  276. rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +46 -0
  277. rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +80 -0
  278. rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +43 -0
  279. rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +61 -0
  280. rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +47 -0
  281. rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +46 -0
  282. rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +52 -0
  283. rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +42 -0
  284. rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +65 -0
  285. rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +46 -0
  286. rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +47 -0
  287. rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +45 -0
  288. rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +46 -0
  289. rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +48 -0
  290. rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +50 -0
  291. rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +59 -0
  292. rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +48 -0
  293. rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +108 -0
  294. rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +57 -0
  295. rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +51 -0
  296. rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +50 -0
  297. rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +46 -0
  298. rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +42 -0
  299. rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +93 -0
  300. rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +73 -0
  301. rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +52 -0
  302. rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +45 -0
  303. rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +46 -0
  304. rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +54 -0
  305. rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +48 -0
  306. rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +70 -0
  307. rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +48 -0
  308. rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +95 -0
  309. rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +55 -0
  310. rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +74 -0
  311. rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +78 -0
  312. rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +49 -0
  313. rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +124 -0
  314. rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +60 -0
  315. rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +53 -0
  316. rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +56 -0
  317. rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +67 -0
  318. rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +50 -0
  319. rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +46 -0
  320. rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +92 -0
  321. rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +42 -0
  322. rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +46 -0
  323. rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +147 -0
  324. rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +78 -0
  325. rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +53 -0
  326. rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +74 -0
  327. rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +56 -0
  328. rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +46 -0
  329. rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +68 -0
  330. rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +48 -0
  331. rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +149 -0
  332. rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +106 -0
  333. rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +47 -0
  334. rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +45 -0
  335. rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +105 -0
  336. rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +52 -0
  337. rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +106 -0
  338. rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +30 -0
  339. rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +75 -0
  340. rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +49 -0
  341. rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +45 -0
  342. rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +38 -0
  343. rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +44 -0
  344. rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +46 -0
  345. rucio/db/sqla/models.py +1834 -0
  346. rucio/db/sqla/sautils.py +48 -0
  347. rucio/db/sqla/session.py +470 -0
  348. rucio/db/sqla/types.py +207 -0
  349. rucio/db/sqla/util.py +521 -0
  350. rucio/rse/__init__.py +97 -0
  351. rucio/rse/protocols/__init__.py +14 -0
  352. rucio/rse/protocols/cache.py +123 -0
  353. rucio/rse/protocols/dummy.py +112 -0
  354. rucio/rse/protocols/gfal.py +701 -0
  355. rucio/rse/protocols/globus.py +243 -0
  356. rucio/rse/protocols/gsiftp.py +93 -0
  357. rucio/rse/protocols/http_cache.py +83 -0
  358. rucio/rse/protocols/mock.py +124 -0
  359. rucio/rse/protocols/ngarc.py +210 -0
  360. rucio/rse/protocols/posix.py +251 -0
  361. rucio/rse/protocols/protocol.py +530 -0
  362. rucio/rse/protocols/rclone.py +365 -0
  363. rucio/rse/protocols/rfio.py +137 -0
  364. rucio/rse/protocols/srm.py +339 -0
  365. rucio/rse/protocols/ssh.py +414 -0
  366. rucio/rse/protocols/storm.py +207 -0
  367. rucio/rse/protocols/webdav.py +547 -0
  368. rucio/rse/protocols/xrootd.py +295 -0
  369. rucio/rse/rsemanager.py +752 -0
  370. rucio/tests/__init__.py +14 -0
  371. rucio/tests/common.py +244 -0
  372. rucio/tests/common_server.py +132 -0
  373. rucio/transfertool/__init__.py +14 -0
  374. rucio/transfertool/fts3.py +1484 -0
  375. rucio/transfertool/globus.py +200 -0
  376. rucio/transfertool/globus_library.py +182 -0
  377. rucio/transfertool/mock.py +81 -0
  378. rucio/transfertool/transfertool.py +212 -0
  379. rucio/vcsversion.py +11 -0
  380. rucio/version.py +46 -0
  381. rucio/web/__init__.py +14 -0
  382. rucio/web/rest/__init__.py +14 -0
  383. rucio/web/rest/flaskapi/__init__.py +14 -0
  384. rucio/web/rest/flaskapi/authenticated_bp.py +28 -0
  385. rucio/web/rest/flaskapi/v1/__init__.py +14 -0
  386. rucio/web/rest/flaskapi/v1/accountlimits.py +234 -0
  387. rucio/web/rest/flaskapi/v1/accounts.py +1088 -0
  388. rucio/web/rest/flaskapi/v1/archives.py +100 -0
  389. rucio/web/rest/flaskapi/v1/auth.py +1642 -0
  390. rucio/web/rest/flaskapi/v1/common.py +385 -0
  391. rucio/web/rest/flaskapi/v1/config.py +305 -0
  392. rucio/web/rest/flaskapi/v1/credentials.py +213 -0
  393. rucio/web/rest/flaskapi/v1/dids.py +2204 -0
  394. rucio/web/rest/flaskapi/v1/dirac.py +116 -0
  395. rucio/web/rest/flaskapi/v1/export.py +77 -0
  396. rucio/web/rest/flaskapi/v1/heartbeats.py +129 -0
  397. rucio/web/rest/flaskapi/v1/identities.py +263 -0
  398. rucio/web/rest/flaskapi/v1/import.py +133 -0
  399. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +315 -0
  400. rucio/web/rest/flaskapi/v1/locks.py +360 -0
  401. rucio/web/rest/flaskapi/v1/main.py +83 -0
  402. rucio/web/rest/flaskapi/v1/meta.py +226 -0
  403. rucio/web/rest/flaskapi/v1/metrics.py +37 -0
  404. rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
  405. rucio/web/rest/flaskapi/v1/ping.py +89 -0
  406. rucio/web/rest/flaskapi/v1/redirect.py +366 -0
  407. rucio/web/rest/flaskapi/v1/replicas.py +1866 -0
  408. rucio/web/rest/flaskapi/v1/requests.py +841 -0
  409. rucio/web/rest/flaskapi/v1/rses.py +2204 -0
  410. rucio/web/rest/flaskapi/v1/rules.py +824 -0
  411. rucio/web/rest/flaskapi/v1/scopes.py +161 -0
  412. rucio/web/rest/flaskapi/v1/subscriptions.py +646 -0
  413. rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
  414. rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
  415. rucio/web/rest/flaskapi/v1/tmp_dids.py +115 -0
  416. rucio/web/rest/flaskapi/v1/traces.py +100 -0
  417. rucio/web/rest/flaskapi/v1/vos.py +280 -0
  418. rucio/web/rest/main.py +19 -0
  419. rucio/web/rest/metrics.py +28 -0
  420. rucio-32.8.6.data/data/rucio/etc/alembic.ini.template +71 -0
  421. rucio-32.8.6.data/data/rucio/etc/alembic_offline.ini.template +74 -0
  422. rucio-32.8.6.data/data/rucio/etc/globus-config.yml.template +5 -0
  423. rucio-32.8.6.data/data/rucio/etc/ldap.cfg.template +30 -0
  424. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
  425. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
  426. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
  427. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
  428. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
  429. rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
  430. rucio-32.8.6.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
  431. rucio-32.8.6.data/data/rucio/etc/rucio.cfg.atlas.client.template +42 -0
  432. rucio-32.8.6.data/data/rucio/etc/rucio.cfg.template +257 -0
  433. rucio-32.8.6.data/data/rucio/etc/rucio_multi_vo.cfg.template +234 -0
  434. rucio-32.8.6.data/data/rucio/requirements.txt +55 -0
  435. rucio-32.8.6.data/data/rucio/tools/bootstrap.py +34 -0
  436. rucio-32.8.6.data/data/rucio/tools/merge_rucio_configs.py +147 -0
  437. rucio-32.8.6.data/data/rucio/tools/reset_database.py +40 -0
  438. rucio-32.8.6.data/scripts/rucio +2540 -0
  439. rucio-32.8.6.data/scripts/rucio-abacus-account +75 -0
  440. rucio-32.8.6.data/scripts/rucio-abacus-collection-replica +47 -0
  441. rucio-32.8.6.data/scripts/rucio-abacus-rse +79 -0
  442. rucio-32.8.6.data/scripts/rucio-admin +2434 -0
  443. rucio-32.8.6.data/scripts/rucio-atropos +61 -0
  444. rucio-32.8.6.data/scripts/rucio-auditor +199 -0
  445. rucio-32.8.6.data/scripts/rucio-automatix +51 -0
  446. rucio-32.8.6.data/scripts/rucio-bb8 +58 -0
  447. rucio-32.8.6.data/scripts/rucio-c3po +86 -0
  448. rucio-32.8.6.data/scripts/rucio-cache-client +135 -0
  449. rucio-32.8.6.data/scripts/rucio-cache-consumer +43 -0
  450. rucio-32.8.6.data/scripts/rucio-conveyor-finisher +59 -0
  451. rucio-32.8.6.data/scripts/rucio-conveyor-poller +67 -0
  452. rucio-32.8.6.data/scripts/rucio-conveyor-preparer +38 -0
  453. rucio-32.8.6.data/scripts/rucio-conveyor-receiver +44 -0
  454. rucio-32.8.6.data/scripts/rucio-conveyor-stager +77 -0
  455. rucio-32.8.6.data/scripts/rucio-conveyor-submitter +140 -0
  456. rucio-32.8.6.data/scripts/rucio-conveyor-throttler +105 -0
  457. rucio-32.8.6.data/scripts/rucio-dark-reaper +54 -0
  458. rucio-32.8.6.data/scripts/rucio-dumper +159 -0
  459. rucio-32.8.6.data/scripts/rucio-follower +45 -0
  460. rucio-32.8.6.data/scripts/rucio-hermes +55 -0
  461. rucio-32.8.6.data/scripts/rucio-judge-cleaner +90 -0
  462. rucio-32.8.6.data/scripts/rucio-judge-evaluator +138 -0
  463. rucio-32.8.6.data/scripts/rucio-judge-injector +45 -0
  464. rucio-32.8.6.data/scripts/rucio-judge-repairer +45 -0
  465. rucio-32.8.6.data/scripts/rucio-kronos +45 -0
  466. rucio-32.8.6.data/scripts/rucio-light-reaper +53 -0
  467. rucio-32.8.6.data/scripts/rucio-minos +54 -0
  468. rucio-32.8.6.data/scripts/rucio-minos-temporary-expiration +51 -0
  469. rucio-32.8.6.data/scripts/rucio-necromancer +121 -0
  470. rucio-32.8.6.data/scripts/rucio-oauth-manager +64 -0
  471. rucio-32.8.6.data/scripts/rucio-reaper +84 -0
  472. rucio-32.8.6.data/scripts/rucio-replica-recoverer +249 -0
  473. rucio-32.8.6.data/scripts/rucio-storage-consistency-actions +75 -0
  474. rucio-32.8.6.data/scripts/rucio-transmogrifier +78 -0
  475. rucio-32.8.6.data/scripts/rucio-undertaker +77 -0
  476. rucio-32.8.6.dist-info/METADATA +83 -0
  477. rucio-32.8.6.dist-info/RECORD +481 -0
  478. rucio-32.8.6.dist-info/WHEEL +5 -0
  479. rucio-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
  480. rucio-32.8.6.dist-info/licenses/LICENSE +201 -0
  481. rucio-32.8.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,753 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ Storage-Consistency-Actions is a daemon to delete dark files, and re-subscribe the missing ones, identified previously in a Storage-Consistency-Scanner run.
18
+ """
19
+
20
+ import csv
21
+ import glob
22
+ import json
23
+ import logging
24
+ import os
25
+ import re
26
+ import socket
27
+ import threading
28
+ import time
29
+ import traceback
30
+ from datetime import datetime
31
+ from typing import TYPE_CHECKING
32
+
33
+ from sqlalchemy.exc import DatabaseError, IntegrityError
34
+ from sqlalchemy.orm.exc import FlushError
35
+
36
+ from rucio.common import exception
37
+ from rucio.common.logging import formatted_logger, setup_logging
38
+ from rucio.common.types import InternalAccount, InternalScope
39
+ from rucio.common.utils import daemon_sleep
40
+ from rucio.core.heartbeat import live, die, sanity_check
41
+ from rucio.core.monitor import MetricManager
42
+ from rucio.core.quarantined_replica import add_quarantined_replicas
43
+ from rucio.core.replica import __exist_replicas, update_replicas_states
44
+ from rucio.core.rse import list_rses, get_rse_id
45
+ # FIXME: these are needed by local version of declare_bad_file_replicas()
46
+ # TODO: remove after move of this code to core/replica.py - see https://github.com/rucio/rucio/pull/5068
47
+ from rucio.db.sqla import models
48
+ from rucio.db.sqla.constants import (ReplicaState, BadFilesStatus)
49
+ from rucio.db.sqla.session import transactional_session
50
+ from rucio.rse.rsemanager import lfns2pfns, get_rse_info, parse_pfns
51
+
52
+ if TYPE_CHECKING:
53
+ from types import FrameType
54
+ from typing import Optional
55
+
56
+ METRICS = MetricManager(module=__name__)
57
+ graceful_stop = threading.Event()
58
+ DAEMON_NAME = 'storage-consistency-actions'
59
+
60
+ # FIXME: declare_bad_file_replicas will be used directly from core/replica.py when handling of DID is added there
61
+ # TODO: remove after move to core/replica.py
62
+
63
+
64
+ @transactional_session
65
+ def declare_bad_file_replicas(dids, rse_id, reason, issuer,
66
+ status=BadFilesStatus.BAD, scheme=None, *, session=None):
67
+ """
68
+ Declare a list of bad replicas.
69
+
70
+ :param dids: The list of DIDs.
71
+ :param rse_id: The RSE id.
72
+ :param reason: The reason of the loss.
73
+ :param issuer: The issuer account.
74
+ :param status: Either BAD or SUSPICIOUS.
75
+ :param scheme: The scheme of the PFNs.
76
+ :param session: The database session in use.
77
+ """
78
+ unknown_replicas = []
79
+ replicas = []
80
+ for did in dids:
81
+ scope = InternalScope(did['scope'], vo=issuer.vo)
82
+ name = did['name']
83
+ path = None
84
+ scope, name, path, exists, already_declared, size = __exist_replicas(rse_id, [(scope, name, path)],
85
+ session=session)[0]
86
+ if exists and ((str(status) == str(BadFilesStatus.BAD) and not
87
+ already_declared) or str(status) == str(BadFilesStatus.SUSPICIOUS)):
88
+ replicas.append({'scope': scope, 'name': name, 'rse_id': rse_id,
89
+ 'state': ReplicaState.BAD})
90
+ new_bad_replica = models.BadReplicas(scope=scope, name=name, rse_id=rse_id,
91
+ reason=reason, state=status, account=issuer,
92
+ bytes=size)
93
+ new_bad_replica.save(session=session, flush=False)
94
+ session.query(models.Source).filter_by(scope=scope, name=name,
95
+ rse_id=rse_id).delete(synchronize_session=False)
96
+ else:
97
+ if already_declared:
98
+ unknown_replicas.append('%s:%s %s' % (did['scope'], did['name'],
99
+ 'Already declared'))
100
+ else:
101
+ unknown_replicas.append('%s:%s %s' % (did['scope'], did['name'],
102
+ 'Unknown replica'))
103
+ if str(status) == str(BadFilesStatus.BAD):
104
+ # For BAD file, we modify the replica state, not for suspicious
105
+ try:
106
+ # there shouldn't be any exceptions since all replicas exist
107
+ update_replicas_states(replicas, session=session)
108
+ except exception.UnsupportedOperation:
109
+ raise exception.ReplicaNotFound("One or several replicas don't exist.")
110
+ try:
111
+ session.flush()
112
+ except IntegrityError as error:
113
+ raise exception.RucioException(error.args)
114
+ except DatabaseError as error:
115
+ raise exception.RucioException(error.args)
116
+ except FlushError as error:
117
+ raise exception.RucioException(error.args)
118
+
119
+ return unknown_replicas
120
+
121
+
122
+ # TODO: This is Igor's Stats class.It will be factored out as a separate class in a future version of the code.
123
+ # - Igor Mandrichenko <ivm@fnal.gov>, 2018
124
+
125
+ class Stats(object):
126
+
127
+ def __init__(self, path):
128
+ self.path = path
129
+ self.Data = {}
130
+
131
+ def __getitem__(self, name):
132
+ return self.Data[name]
133
+
134
+ def __setitem__(self, name, value):
135
+ self.Data[name] = value
136
+ self.save()
137
+
138
+ def get(self, name, default=None):
139
+ return self.Data.get(name, default)
140
+
141
+ def update(self, data):
142
+ self.Data.update(data)
143
+ self.save()
144
+
145
+ def save(self):
146
+ try:
147
+ with open(self.path, "r") as f:
148
+ data = f.read()
149
+ except:
150
+ data = ""
151
+ data = json.loads(data or "{}")
152
+ data.update(self.Data)
153
+ open(self.path, "w").write(json.dumps(data, indent=4))
154
+
155
+
156
+ def write_stats(my_stats, stats_file, stats_key=None):
157
+ if stats_file:
158
+ stats = {}
159
+ if os.path.isfile(stats_file):
160
+ with open(stats_file, "r") as f:
161
+ stats = json.loads(f.read())
162
+ if stats_key:
163
+ stats[stats_key] = my_stats
164
+ else:
165
+ stats.update(my_stats)
166
+ open(stats_file, "w").write(json.dumps(stats))
167
+ # TODO: Consider throwing an error here if stats_file is not defined
168
+ # TODO: Consider breaking the logic into two functions, following discussion in https://github.com/rucio/rucio/pull/5120#discussion_r792673599
169
+
170
+
171
+ def cmp2dark(new_list, old_list, comm_list, stats_file):
172
+
173
+ t0 = time.time()
174
+ stats_key = "cmp2dark"
175
+ my_stats = stats = None
176
+
177
+ with open(new_list, "r") as a_list, open(old_list, "r") as b_list,\
178
+ open(comm_list, "w") as out_list:
179
+
180
+ if stats_file is not None:
181
+ stats = Stats(stats_file)
182
+ my_stats = {
183
+ "elapsed": None,
184
+ "start_time": t0,
185
+ "end_time": None,
186
+ "new_list": new_list,
187
+ "old_list": old_list,
188
+ "out_list": out_list.name,
189
+ "status": "started"
190
+ }
191
+ stats[stats_key] = my_stats
192
+
193
+ a_set = set(line.strip() for line in a_list)
194
+ b_set = set(line.strip() for line in b_list)
195
+
196
+
197
+ # The intersection of the two sets is what can be deleted
198
+ out_set = a_set & b_set
199
+ out_list.writelines("\n".join(sorted(list(out_set))))
200
+
201
+ t1 = time.time()
202
+
203
+ if stats_file:
204
+ my_stats.update({
205
+ "elapsed": t1 - t0,
206
+ "end_time": t1,
207
+ "status": "done"
208
+ })
209
+ stats[stats_key] = my_stats
210
+
211
+
212
+ # TODO: Changes suggested in https://github.com/rucio/rucio/pull/5120#discussion_r792681245
213
+ def parse_filename(fn):
214
+ # filename looks like this:
215
+ #
216
+ # <rse>_%Y_%m_%d_%H_%M_<type>.<extension>
217
+ #
218
+ fn, ext = fn.rsplit(".", 1)
219
+ parts = fn.split("_")
220
+ typ = parts[-1]
221
+ timestamp_parts = parts[-6:-1]
222
+ timestamp = "_".join(timestamp_parts)
223
+ rse = "_".join(parts[:-6])
224
+ return rse, timestamp, typ, ext
225
+
226
+
227
+ def list_cc_scanned_rses(path):
228
+ files = glob.glob(f"{path}/*_stats.json")
229
+ rses = set()
230
+ for path in files:
231
+ fn = path.rsplit("/", 1)[-1]
232
+ rse, timestamp, typ, ext = parse_filename(fn)
233
+ rses.add(rse)
234
+ return sorted(list(rses))
235
+
236
+
237
+ def list_runs_by_age(path, rse, reffile):
238
+ files = glob.glob(f"{path}/{rse}_*_stats.json")
239
+ r, reftimestamp, typ, ext = parse_filename(reffile)
240
+ reftime = datetime.strptime(reftimestamp, '%Y_%m_%d_%H_%M')
241
+ runs = {}
242
+ for path in files:
243
+ fn = path.rsplit("/", 1)[-1]
244
+ if os.stat(path).st_size > 0:
245
+ r, timestamp, typ, ext = parse_filename(fn)
246
+ filetime = datetime.strptime(timestamp, '%Y_%m_%d_%H_%M')
247
+ fileagedays = (reftime - filetime).days
248
+ if r == rse:
249
+ # if the RSE was X, then rses like X_Y will appear in this list too,
250
+ # so double check that we get the right RSE
251
+ runs.update({path: fileagedays})
252
+
253
+ return {k: v for k, v in sorted(runs.items(), reverse=True)}
254
+
255
+
256
+ def list_runs(path, rse, nlast=0):
257
+ files = glob.glob(f"{path}/{rse}_*_stats.json")
258
+ runs = []
259
+ for path in files:
260
+ fn = path.rsplit("/", 1)[-1]
261
+ if os.stat(path).st_size > 0:
262
+ r, timestamp, typ, ext = parse_filename(fn)
263
+ if r == rse:
264
+ # if the RSE was X, then rses like X_Y will appear in this list too,
265
+ # so double check that we get the right RSE
266
+ runs.append(path)
267
+ if nlast == 0:
268
+ nlast = len(runs)
269
+ return sorted(runs, reverse=False)[-nlast:]
270
+
271
+
272
+ def list_unprocessed_runs(path, rse, nlast=0):
273
+ files = glob.glob(f"{path}/{rse}_*_stats.json")
274
+ unproc_runs = []
275
+ for path in files:
276
+ fn = path.rsplit("/", 1)[-1]
277
+ if os.stat(path).st_size > 0:
278
+ r, timestamp, typ, ext = parse_filename(fn)
279
+ if r == rse:
280
+ # if the RSE was X, then rses like X_Y will appear in this list too,
281
+ # so double check that we get the right RSE
282
+ if not was_cc_attempted(path):
283
+ unproc_runs.append(timestamp)
284
+ if nlast == 0:
285
+ nlast = len(unproc_runs)
286
+ return sorted(unproc_runs, reverse=True)[-nlast:]
287
+
288
+
289
+ def was_cc_attempted(stats_file):
290
+ try:
291
+ f = open(stats_file, "r")
292
+ except:
293
+ print("get_data: error ", stats_file)
294
+ return None
295
+ stats = json.loads(f.read())
296
+ if "cc_dark" in stats or "cc_miss" in stats:
297
+ return True
298
+ else:
299
+ return False
300
+
301
+
302
+ def was_cc_processed(stats_file):
303
+ try:
304
+ f = open(stats_file, "r")
305
+ except:
306
+ print("get_data: error ", stats_file)
307
+ return None
308
+ stats = json.loads(f.read())
309
+ cc_dark_status = ''
310
+ cc_miss_status = ''
311
+ if "cc_dark" in stats:
312
+ if "status" in stats['cc_dark']:
313
+ cc_dark_status = stats['cc_dark']['status']
314
+ if "cc_miss" in stats:
315
+ if "status" in stats['cc_miss']:
316
+ cc_miss_status = stats['cc_miss']['status']
317
+ if cc_dark_status == 'done' or cc_miss_status == 'done':
318
+ return True
319
+ else:
320
+ return False
321
+
322
+
323
+ def process_dark_files(path, scope, rse, latest_run, max_dark_fraction,
324
+ max_files_at_site, old_enough_run, force_proceed):
325
+
326
+ """
327
+ Process the Dark Files.
328
+ """
329
+
330
+ prefix = 'storage-consistency-actions (process_dark_files())'
331
+ logger = formatted_logger(logging.log, prefix + '%s')
332
+
333
+ # Create a cc_dark section in the stats file
334
+
335
+ t0 = time.time()
336
+ stats_key = "cc_dark"
337
+ cc_stats = stats = None
338
+ stats = Stats(latest_run)
339
+ cc_stats = {
340
+ "start_time": t0,
341
+ "end_time": None,
342
+ "initial_dark_files": 0,
343
+ "confirmed_dark_files": 0,
344
+ "x-check_run": old_enough_run,
345
+ "status": "started"
346
+ }
347
+ stats[stats_key] = cc_stats
348
+
349
+ # Compare the two lists, and take only the dark files that are in both
350
+ latest_dark = re.sub('_stats.json$', '_D.list', latest_run)
351
+ old_enough_dark = re.sub('_stats.json$', '_D.list', old_enough_run)
352
+ logger(logging.INFO, 'latest_dark = %s' % latest_dark)
353
+ logger(logging.INFO, 'old_enough_dark = %s' % old_enough_dark)
354
+ confirmed_dark = re.sub('_stats.json$', '_DeletionList.csv', latest_run)
355
+ cmp2dark(new_list=latest_dark, old_list=old_enough_dark,
356
+ comm_list=confirmed_dark, stats_file=latest_run)
357
+
358
+ ###
359
+ # SAFEGUARD
360
+ # If a large fraction (larger than 'max_dark_fraction') of the files at a site
361
+ # are reported as 'dark', do NOT proceed with the deletion.
362
+ # Instead, put a warning in the _stats.json file, so that an operator can have a look.
363
+ ###
364
+
365
+ # Get the number of files recorded by the scanner
366
+ dark_files = sum(1 for line in open(latest_dark))
367
+ confirmed_dark_files = sum(1 for line in open(confirmed_dark))
368
+ logger(logging.INFO, 'dark_files %d' % dark_files)
369
+ logger(logging.INFO, 'confirmed_dark_files %d' % confirmed_dark_files)
370
+ logger(logging.INFO, 'confirmed_dark_files/max_files_at_sit = %f'
371
+ % (confirmed_dark_files / max_files_at_site))
372
+ logger(logging.INFO, 'max_dark_fraction configured for this RSE: %f'
373
+ % max_dark_fraction)
374
+
375
+ # Labels for the Prometheus counters/gauges
376
+ labels = {'rse': rse}
377
+
378
+ METRICS.gauge('actions_dark_files_found.{rse}').labels(**labels).set(dark_files)
379
+ METRICS.gauge('actions_dark_files_confirmed.{rse}').labels(**labels).set(confirmed_dark_files)
380
+
381
+ deleted_files = 0
382
+ if confirmed_dark_files / max_files_at_site < max_dark_fraction or force_proceed is True:
383
+ logger(logging.INFO, 'Can proceed with dark files deletion')
384
+
385
+ # Then, do the real deletion (code from DeleteReplicas.py)
386
+ issuer = InternalAccount('root')
387
+ with open(confirmed_dark, 'r') as csvfile:
388
+ reader = csv.reader(csvfile)
389
+ for name, in reader:
390
+ logger(logging.INFO, 'Processing a dark file:\n RSE %s Scope: %s Name: %s'
391
+ % (rse, scope, name))
392
+ rse_id = get_rse_id(rse=rse)
393
+ Intscope = InternalScope(scope=scope, vo=issuer.vo)
394
+ lfns = [{'scope': scope, 'name': name}]
395
+
396
+ attributes = get_rse_info(rse=rse)
397
+ pfns = lfns2pfns(rse_settings=attributes, lfns=lfns, operation='delete')
398
+ pfn_key = scope + ':' + name
399
+ url = pfns[pfn_key]
400
+ urls = [url]
401
+ paths = parse_pfns(attributes, urls, operation='delete')
402
+ replicas = [{'scope': Intscope, 'rse_id': rse_id, 'name': name,
403
+ 'path': paths[url]['path'] + paths[url]['name']}]
404
+ add_quarantined_replicas(rse_id, replicas, session=None)
405
+ deleted_files += 1
406
+ METRICS.counter('actions_dark_files_deleted_counter.{rse}').labels(**labels).inc()
407
+
408
+ # Update the stats
409
+ t1 = time.time()
410
+
411
+ cc_stats.update({
412
+ "end_time": t1,
413
+ "initial_dark_files": dark_files,
414
+ "confirmed_dark_files": deleted_files,
415
+ "status": "done"
416
+ })
417
+ stats[stats_key] = cc_stats
418
+ else:
419
+ darkperc = 100. * confirmed_dark_files / max_files_at_site
420
+ logger(logging.WARNING, '\n ATTENTION: Too many DARK files! (%3.2f%%) \n\
421
+ Stopping and asking for operators help.' % darkperc)
422
+
423
+ # Update the stats
424
+ t1 = time.time()
425
+
426
+ cc_stats.update({
427
+ "end_time": t1,
428
+ "initial_dark_files": dark_files,
429
+ "confirmed_dark_files": 0,
430
+ "status": "ABORTED",
431
+ "aborted_reason": "%3.2f%% dark" % darkperc,
432
+ })
433
+ stats[stats_key] = cc_stats
434
+ METRICS.gauge('actions_dark_files_deleted.{rse}').labels(**labels).set(deleted_files)
435
+
436
+
437
+ def process_miss_files(path, scope, rse, latest_run, max_miss_fraction,
438
+ max_files_at_site, old_enough_run, force_proceed):
439
+
440
+ """
441
+ Process the Missing Replicas.
442
+ """
443
+
444
+ prefix = 'storage-consistency-actions (process_miss_files())'
445
+ logger = formatted_logger(logging.log, prefix + '%s')
446
+
447
+ latest_miss = re.sub('_stats.json$', '_M.list', latest_run)
448
+ logger(logging.INFO, 'latest_missing = %s' % latest_miss)
449
+
450
+ # Create a cc_miss section in the stats file
451
+
452
+ t0 = time.time()
453
+ stats_key = "cc_miss"
454
+ cc_stats = stats = None
455
+ stats = Stats(latest_run)
456
+ cc_stats = {
457
+ "start_time": t0,
458
+ "end_time": None,
459
+ "initial_miss_files": 0,
460
+ "confirmed_miss_files": 0,
461
+ "x-check_run": old_enough_run,
462
+ "status": "started"
463
+ }
464
+ stats[stats_key] = cc_stats
465
+
466
+ ###
467
+ # SAFEGUARD
468
+ # If a large fraction (larger than 'max_miss_fraction') of the files at a site are reported as
469
+ # 'missing', do NOT proceed with the invalidation.
470
+ # Instead, put a warning in the _stats.json file, so that an operator can have a look.
471
+ ###
472
+
473
+ miss_files = sum(1 for line in open(latest_miss))
474
+ logger(logging.INFO, 'miss_files = %d' % miss_files)
475
+ logger(logging.INFO, 'miss_files/max_files_at_site = %f' % (miss_files / max_files_at_site))
476
+ logger(logging.INFO, 'max_miss_fraction configured for this RSE (in %%): %f' % max_miss_fraction)
477
+
478
+ labels = {'rse': rse}
479
+ METRICS.gauge('actions_miss_files_found.{rse}').labels(**labels).set(miss_files)
480
+
481
+ invalidated_files = 0
482
+ if miss_files / max_files_at_site < max_miss_fraction or force_proceed is True:
483
+ logger(logging.INFO, 'Can proceed with missing files retransfer')
484
+
485
+ issuer = InternalAccount('root')
486
+ with open(latest_miss, 'r') as csvfile:
487
+ reader = csv.reader(csvfile)
488
+ reason = "invalidating damaged/missing replica"
489
+ for name, in reader:
490
+ logger(logging.INFO, 'Processing invalid replica:\n RSE: %s Scope: %s Name: %s'
491
+ % (rse, scope, name))
492
+
493
+ rse_id = get_rse_id(rse=rse)
494
+ dids = [{'scope': scope, 'name': name}]
495
+ declare_bad_file_replicas(dids=dids, rse_id=rse_id, reason=reason,
496
+ issuer=issuer)
497
+ invalidated_files += 1
498
+ METRICS.counter('actions_miss_files_to_retransfer_counter.{rse}').labels(**labels).inc()
499
+
500
+ # TODO: The stats updating can be refactored in a future version of the Stats class.
501
+ # See: https://github.com/rucio/rucio/pull/5120#discussion_r792688019
502
+ # Update the stats
503
+ t1 = time.time()
504
+
505
+ cc_stats.update({
506
+ "end_time": t1,
507
+ "initial_miss_files": miss_files,
508
+ "confirmed_miss": invalidated_files,
509
+ "status": "done"
510
+ })
511
+ stats[stats_key] = cc_stats
512
+
513
+ else:
514
+ missperc = 100. * miss_files / max_files_at_site
515
+ logger(logging.WARNING, '\n Too many MISS files (%3.2f%%)!\n\
516
+ Stopping and asking for operators help.' % missperc)
517
+
518
+ # Update the stats
519
+ t1 = time.time()
520
+
521
+ cc_stats.update({
522
+ "end_time": t1,
523
+ "initial_miss_files": miss_files,
524
+ "confirmed_miss_files": 0,
525
+ "status": "ABORTED",
526
+ "aborted_reason": "%3.2f%% miss" % missperc,
527
+ })
528
+ stats[stats_key] = cc_stats
529
+ METRICS.gauge('actions_miss_files_to_retransfer.{rse}').labels(**labels).set(invalidated_files)
530
+
531
+
532
+ def deckard(scope, rse, dark_min_age, dark_threshold_percent, miss_threshold_percent,
533
+ force_proceed, scanner_files_path):
534
+
535
+ """
536
+ The core of CC actions.
537
+ Use the results of the CC Scanner to check one RSE for confirmed dark files and delete them.
538
+ Re-subscribe missing files.
539
+ """
540
+
541
+ prefix = 'storage-consistency-actions (running original deckard code)'
542
+ logger = formatted_logger(logging.log, prefix + '%s')
543
+ logger(logging.INFO, 'Now running the original deckard code...')
544
+
545
+ path = scanner_files_path
546
+ minagedark = dark_min_age
547
+ max_dark_fraction = dark_threshold_percent
548
+ max_miss_fraction = miss_threshold_percent
549
+ logger(logging.INFO, 'Scanner Output Path: %s \n minagedark: %d \n max_dark_fraction: %f\
550
+ \n max_miss_fraction: %f' % (path, minagedark, max_dark_fraction, max_miss_fraction))
551
+
552
+ scanner_files = 0
553
+ dbdump_before_files = 0
554
+ dbdump_after_files = 0
555
+
556
+ # Check if we have any scans available for that RSE
557
+ if rse in list_cc_scanned_rses(path):
558
+
559
+ # Have any of them still not been processed?
560
+ # (no CC_dark or CC-miss sections in _stats.json)
561
+ np_runs = list_unprocessed_runs(path, rse)
562
+ logger(logging.INFO, 'Found %d unprocessed runs for RSE: %s' % (len(np_runs), rse))
563
+
564
+ latest_run = list_runs(path, rse, 1)[0]
565
+
566
+ # Get the number of files recorded by the scanner
567
+ logger(logging.INFO, 'latest_run %s' % latest_run)
568
+ with open(latest_run, "r") as f:
569
+ fstats = json.loads(f.read())
570
+ if "scanner" in fstats:
571
+ scanner_stats = fstats["scanner"]
572
+ if "total_files" in scanner_stats:
573
+ scanner_files = scanner_stats["total_files"]
574
+ else:
575
+ scanner_files = 0
576
+ for root_info in scanner_stats["roots"]:
577
+ scanner_files += root_info["files"]
578
+ if "dbdump_before" in fstats:
579
+ dbdump_before_files = fstats["dbdump_before"]["files"]
580
+ if "dbdump_after" in fstats:
581
+ dbdump_after_files = fstats["dbdump_after"]["files"]
582
+
583
+ max_files_at_site = max(scanner_files, dbdump_before_files, dbdump_after_files)
584
+ if max_files_at_site == 0:
585
+ logger(logging.WARNING, '\n No files reported by scanner for this run.\
586
+ Will skip processing.')
587
+
588
+ logger(logging.INFO, 'scanner_files: %d \n dbdump_before_files: %d\
589
+ \n dbdump_after_files: %d \n max_files_at_site: %d' %
590
+ (scanner_files, dbdump_before_files, dbdump_after_files, max_files_at_site))
591
+
592
+
593
+ # Was the latest run ever attempted to be processed?
594
+ logger(logging.INFO, 'Was the latest run %s attempted to be processed already? - %s'
595
+ % (latest_run, was_cc_attempted(latest_run)))
596
+ if max_files_at_site > 0 and (was_cc_attempted(latest_run) is False or force_proceed is True):
597
+ logger(logging.INFO, 'Will try to process the run')
598
+
599
+ # Is there another run, at least "minagedark" old, for this RSE?
600
+ old_enough_run = None
601
+ d = list_runs_by_age(path, rse, latest_run)
602
+ if len([k for k in d if d[k] > minagedark]) > 0:
603
+ # i.e. there is another dark run with appropriate age
604
+ old_enough_run = [k for k in d if d[k] > minagedark][0]
605
+ logger(logging.INFO, 'Found another run %d days older than the latest.\
606
+ \n Will compare the dark files in the two.' % minagedark)
607
+ logger(logging.INFO, 'The first %d days older run is: %s'
608
+ % (minagedark, old_enough_run))
609
+
610
+ process_dark_files(path, scope, rse, latest_run, max_dark_fraction,
611
+ max_files_at_site, old_enough_run, force_proceed)
612
+ else:
613
+ logger(logging.INFO, 'There is no other run for this RSE at least %d days older,\
614
+ so cannot safely proceed with dark files deleteion.' % minagedark)
615
+
616
+ process_miss_files(path, scope, rse, latest_run, max_miss_fraction,
617
+ max_files_at_site, old_enough_run, force_proceed)
618
+
619
+ else:
620
+ # This run was already processed
621
+ logger(logging.INFO, 'Nothing to do here')
622
+
623
+ else:
624
+ # No scans outputs are available for this RSE
625
+ logger(logging.INFO, 'No scans available for this RSE')
626
+
627
+
628
+ def deckard_loop(scope, rses, dark_min_age, dark_threshold_percent, miss_threshold_percent,
629
+ force_proceed, scanner_files_path):
630
+
631
+ prefix = 'storage-consistency-actions (deckard_loop())'
632
+ logger = formatted_logger(logging.log, prefix + '%s')
633
+ logger(logging.INFO, 'A loop over all RSEs')
634
+ for rse in rses:
635
+ logger(logging.INFO, 'Now processing RSE: %s' % rse)
636
+ deckard(scope, rse, dark_min_age, dark_threshold_percent, miss_threshold_percent,
637
+ force_proceed, scanner_files_path)
638
+
639
+
640
+ def actions_loop(once, scope, rses, sleep_time, dark_min_age, dark_threshold_percent,
641
+ miss_threshold_percent, force_proceed, scanner_files_path):
642
+
643
+ """
644
+ Main loop to apply the CC actions
645
+ """
646
+
647
+ hostname = socket.gethostname()
648
+ pid = os.getpid()
649
+ current_thread = threading.current_thread()
650
+
651
+ heartbeat = live(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
652
+
653
+ # Make an initial heartbeat
654
+ # so that all storage-consistency-actions have the correct worker number on the next try
655
+ prefix = 'storage-consistency-actions[%i/%i] ' %\
656
+ (heartbeat['assign_thread'], heartbeat['nr_threads'])
657
+ logger = formatted_logger(logging.log, prefix + '%s')
658
+ logger(logging.INFO, 'hostname: %s pid: %d current_thread: %s' %
659
+ (hostname, pid, current_thread))
660
+
661
+ graceful_stop.wait(1)
662
+
663
+ while not graceful_stop.is_set():
664
+ try:
665
+ heartbeat = live(executable=DAEMON_NAME, hostname=hostname, pid=pid,
666
+ thread=current_thread)
667
+ logger(logging.INFO, 'heartbeat? %s' % heartbeat)
668
+
669
+ prefix = 'storage-consistency-actions[%i/%i] ' %\
670
+ (heartbeat['assign_thread'], heartbeat['nr_threads'])
671
+ logger(logging.INFO, 'prefix: %s' % prefix)
672
+ start = time.time()
673
+ logger(logging.DEBUG, 'Start time: %f' % start)
674
+
675
+ deckard_loop(scope, rses, dark_min_age, dark_threshold_percent, miss_threshold_percent,
676
+ force_proceed, scanner_files_path)
677
+ daemon_sleep(start_time=start, sleep_time=sleep_time, graceful_stop=graceful_stop,
678
+ logger=logger)
679
+
680
+ except Exception as e:
681
+ traceback.print_exc()
682
+ logger(logging.WARNING, '\n Something went wrong here... %s' % e)
683
+ logger(logging.WARNING, '\n Something went wrong here... %s ' % (e.__class__.__name__))
684
+ if once:
685
+ break
686
+
687
+ die(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
688
+
689
+
690
+ def stop(signum: "Optional[int]" = None, frame: "Optional[FrameType]" = None) -> None:
691
+ """
692
+ Graceful exit.
693
+ """
694
+ graceful_stop.set()
695
+
696
+
697
+ def run(once=False, scope=None, rses=None, sleep_time=60, default_dark_min_age=28, default_dark_threshold_percent=1.0,
698
+ default_miss_threshold_percent=1.0, force_proceed=False, default_scanner_files_path="/var/cache/consistency-dump",
699
+ threads=1):
700
+ """
701
+ Starts up the Consistency-Actions.
702
+ """
703
+
704
+ setup_logging(process_name=DAEMON_NAME)
705
+
706
+ prefix = 'storage-consistency-actions (run())'
707
+ logger = formatted_logger(logging.log, prefix + '%s')
708
+
709
+ # TODO: These variables should be sourced from the RSE config in the future.
710
+ # For now, they are passed as arguments, and to emphasize that fact, we are re-assigning them:
711
+ dark_min_age = default_dark_min_age
712
+ dark_threshold_percent = default_dark_threshold_percent
713
+ miss_threshold_percent = default_miss_threshold_percent
714
+ scanner_files_path = default_scanner_files_path
715
+
716
+ if rses == []:
717
+ logger(logging.INFO, 'NO RSEs passed. Will loop over all writable RSEs.')
718
+
719
+ rses = [rse['rse'] for rse in list_rses({'availability_write': True})]
720
+
721
+ # Could limit it only to Tier-2s:
722
+ # rses = [rse['rse'] for rse in list_rses({'tier': 2, 'availability_write': True})]
723
+
724
+ logging.info('\n RSEs: %s' % rses)
725
+ logger(logging.INFO, '\n RSEs: %s \n run once: %r \n Sleep time: %d \n Dark min age (days): %d\
726
+ \n Dark files threshold %%: %f \n Missing files threshold %%: %f \n Force proceed: %r\
727
+ \n Scanner files path: %s ' % (rses, once, sleep_time, dark_min_age, dark_threshold_percent,
728
+ miss_threshold_percent, force_proceed, scanner_files_path))
729
+
730
+ hostname = socket.gethostname()
731
+ sanity_check(executable=DAEMON_NAME, hostname=hostname)
732
+
733
+ # It was decided that for the time being this daemon is best executed in a single thread
734
+ # TODO: If this decicion is reversed in the future, the following line should be removed.
735
+ threads = 1
736
+
737
+ if once:
738
+ actions_loop(once, scope, rses, sleep_time, dark_min_age, dark_threshold_percent,
739
+ miss_threshold_percent, force_proceed, scanner_files_path)
740
+ else:
741
+ logging.info('Consistency Actions starting %s threads' % str(threads))
742
+ threads = [threading.Thread(target=actions_loop,
743
+ kwargs={'once': once, 'scope': scope, 'rses': rses, 'sleep_time': sleep_time,
744
+ 'dark_min_age': dark_min_age,
745
+ 'dark_threshold_percent': dark_threshold_percent,
746
+ 'miss_threshold_percent': miss_threshold_percent,
747
+ 'force_proceed': force_proceed,
748
+ 'scanner_files_path': scanner_files_path}) for i in range(0, threads)]
749
+ logger(logging.INFO, 'Threads: %d' % len(threads))
750
+ [t.start() for t in threads]
751
+ # Interruptible joins require a timeout.
752
+ while threads[0].is_alive():
753
+ [t.join(timeout=3.14) for t in threads]