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