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.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio/cli/bin_legacy/rucio.py +2825 -0
- rucio/cli/bin_legacy/rucio_admin.py +2500 -0
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +432 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +983 -0
- rucio/client/client.py +120 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +868 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1783 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +452 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +969 -0
- rucio/common/__init__.py +13 -0
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +111 -0
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +788 -0
- rucio/common/constants.py +217 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +237 -0
- rucio/common/dumper/__init__.py +342 -0
- rucio/common/dumper/consistency.py +497 -0
- rucio/common/dumper/data_models.py +362 -0
- rucio/common/dumper/path_parsing.py +75 -0
- rucio/common/exception.py +1208 -0
- rucio/common/extra.py +31 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1409 -0
- rucio/common/plugins.py +185 -0
- rucio/common/policy.py +93 -0
- rucio/common/schema/__init__.py +200 -0
- rucio/common/schema/generic.py +416 -0
- rucio/common/schema/generic_multi_vo.py +395 -0
- rucio/common/stomp_utils.py +423 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +154 -0
- rucio/common/types.py +483 -0
- rucio/common/utils.py +1688 -0
- rucio/core/__init__.py +13 -0
- rucio/core/account.py +496 -0
- rucio/core/account_counter.py +236 -0
- rucio/core/account_limit.py +425 -0
- rucio/core/authentication.py +620 -0
- rucio/core/config.py +437 -0
- rucio/core/credential.py +224 -0
- rucio/core/did.py +3004 -0
- rucio/core/did_meta_plugins/__init__.py +252 -0
- rucio/core/did_meta_plugins/did_column_meta.py +331 -0
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
- rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
- rucio/core/did_meta_plugins/filter_engine.py +672 -0
- rucio/core/did_meta_plugins/json_meta.py +240 -0
- rucio/core/did_meta_plugins/mongo_meta.py +229 -0
- rucio/core/did_meta_plugins/postgres_meta.py +352 -0
- rucio/core/dirac.py +237 -0
- rucio/core/distance.py +187 -0
- rucio/core/exporter.py +59 -0
- rucio/core/heartbeat.py +363 -0
- rucio/core/identity.py +301 -0
- rucio/core/importer.py +260 -0
- rucio/core/lifetime_exception.py +377 -0
- rucio/core/lock.py +577 -0
- rucio/core/message.py +288 -0
- rucio/core/meta_conventions.py +203 -0
- rucio/core/monitor.py +448 -0
- rucio/core/naming_convention.py +195 -0
- rucio/core/nongrid_trace.py +136 -0
- rucio/core/oidc.py +1463 -0
- rucio/core/permission/__init__.py +161 -0
- rucio/core/permission/generic.py +1124 -0
- rucio/core/permission/generic_multi_vo.py +1144 -0
- rucio/core/quarantined_replica.py +224 -0
- rucio/core/replica.py +4483 -0
- rucio/core/replica_sorter.py +362 -0
- rucio/core/request.py +3091 -0
- rucio/core/rse.py +2079 -0
- rucio/core/rse_counter.py +185 -0
- rucio/core/rse_expression_parser.py +459 -0
- rucio/core/rse_selector.py +304 -0
- rucio/core/rule.py +4484 -0
- rucio/core/rule_grouping.py +1620 -0
- rucio/core/scope.py +181 -0
- rucio/core/subscription.py +362 -0
- rucio/core/topology.py +490 -0
- rucio/core/trace.py +375 -0
- rucio/core/transfer.py +1531 -0
- rucio/core/vo.py +169 -0
- rucio/core/volatile_replica.py +151 -0
- rucio/daemons/__init__.py +13 -0
- rucio/daemons/abacus/__init__.py +13 -0
- rucio/daemons/abacus/account.py +116 -0
- rucio/daemons/abacus/collection_replica.py +124 -0
- rucio/daemons/abacus/rse.py +117 -0
- rucio/daemons/atropos/__init__.py +13 -0
- rucio/daemons/atropos/atropos.py +242 -0
- rucio/daemons/auditor/__init__.py +289 -0
- rucio/daemons/auditor/hdfs.py +97 -0
- rucio/daemons/auditor/srmdumps.py +355 -0
- rucio/daemons/automatix/__init__.py +13 -0
- rucio/daemons/automatix/automatix.py +304 -0
- rucio/daemons/badreplicas/__init__.py +13 -0
- rucio/daemons/badreplicas/minos.py +322 -0
- rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
- rucio/daemons/badreplicas/necromancer.py +196 -0
- rucio/daemons/bb8/__init__.py +13 -0
- rucio/daemons/bb8/bb8.py +353 -0
- rucio/daemons/bb8/common.py +759 -0
- rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
- rucio/daemons/bb8/t2_background_rebalance.py +153 -0
- rucio/daemons/cache/__init__.py +13 -0
- rucio/daemons/cache/consumer.py +133 -0
- rucio/daemons/common.py +405 -0
- rucio/daemons/conveyor/__init__.py +13 -0
- rucio/daemons/conveyor/common.py +562 -0
- rucio/daemons/conveyor/finisher.py +529 -0
- rucio/daemons/conveyor/poller.py +394 -0
- rucio/daemons/conveyor/preparer.py +205 -0
- rucio/daemons/conveyor/receiver.py +179 -0
- rucio/daemons/conveyor/stager.py +133 -0
- rucio/daemons/conveyor/submitter.py +403 -0
- rucio/daemons/conveyor/throttler.py +532 -0
- rucio/daemons/follower/__init__.py +13 -0
- rucio/daemons/follower/follower.py +101 -0
- rucio/daemons/hermes/__init__.py +13 -0
- rucio/daemons/hermes/hermes.py +534 -0
- rucio/daemons/judge/__init__.py +13 -0
- rucio/daemons/judge/cleaner.py +159 -0
- rucio/daemons/judge/evaluator.py +185 -0
- rucio/daemons/judge/injector.py +162 -0
- rucio/daemons/judge/repairer.py +154 -0
- rucio/daemons/oauthmanager/__init__.py +13 -0
- rucio/daemons/oauthmanager/oauthmanager.py +198 -0
- rucio/daemons/reaper/__init__.py +13 -0
- rucio/daemons/reaper/dark_reaper.py +282 -0
- rucio/daemons/reaper/reaper.py +739 -0
- rucio/daemons/replicarecoverer/__init__.py +13 -0
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
- rucio/daemons/rsedecommissioner/__init__.py +13 -0
- rucio/daemons/rsedecommissioner/config.py +81 -0
- rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
- rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
- rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
- rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
- rucio/daemons/storage/__init__.py +13 -0
- rucio/daemons/storage/consistency/__init__.py +13 -0
- rucio/daemons/storage/consistency/actions.py +848 -0
- rucio/daemons/tracer/__init__.py +13 -0
- rucio/daemons/tracer/kronos.py +511 -0
- rucio/daemons/transmogrifier/__init__.py +13 -0
- rucio/daemons/transmogrifier/transmogrifier.py +762 -0
- rucio/daemons/undertaker/__init__.py +13 -0
- rucio/daemons/undertaker/undertaker.py +137 -0
- rucio/db/__init__.py +13 -0
- rucio/db/sqla/__init__.py +52 -0
- rucio/db/sqla/constants.py +206 -0
- rucio/db/sqla/migrate_repo/__init__.py +13 -0
- rucio/db/sqla/migrate_repo/env.py +110 -0
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
- rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
- rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
- rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
- rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
- rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
- rucio/db/sqla/models.py +1743 -0
- rucio/db/sqla/sautils.py +55 -0
- rucio/db/sqla/session.py +529 -0
- rucio/db/sqla/types.py +206 -0
- rucio/db/sqla/util.py +543 -0
- rucio/gateway/__init__.py +13 -0
- rucio/gateway/account.py +345 -0
- rucio/gateway/account_limit.py +363 -0
- rucio/gateway/authentication.py +381 -0
- rucio/gateway/config.py +227 -0
- rucio/gateway/credential.py +70 -0
- rucio/gateway/did.py +987 -0
- rucio/gateway/dirac.py +83 -0
- rucio/gateway/exporter.py +60 -0
- rucio/gateway/heartbeat.py +76 -0
- rucio/gateway/identity.py +189 -0
- rucio/gateway/importer.py +46 -0
- rucio/gateway/lifetime_exception.py +121 -0
- rucio/gateway/lock.py +153 -0
- rucio/gateway/meta_conventions.py +98 -0
- rucio/gateway/permission.py +74 -0
- rucio/gateway/quarantined_replica.py +79 -0
- rucio/gateway/replica.py +538 -0
- rucio/gateway/request.py +330 -0
- rucio/gateway/rse.py +632 -0
- rucio/gateway/rule.py +437 -0
- rucio/gateway/scope.py +100 -0
- rucio/gateway/subscription.py +280 -0
- rucio/gateway/vo.py +126 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +194 -0
- rucio/rse/protocols/cache.py +111 -0
- rucio/rse/protocols/dummy.py +100 -0
- rucio/rse/protocols/gfal.py +708 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +361 -0
- rucio/rse/protocols/rclone.py +365 -0
- rucio/rse/protocols/rfio.py +145 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +414 -0
- rucio/rse/protocols/storm.py +195 -0
- rucio/rse/protocols/webdav.py +594 -0
- rucio/rse/protocols/xrootd.py +302 -0
- rucio/rse/rsemanager.py +881 -0
- rucio/rse/translation.py +260 -0
- rucio/tests/__init__.py +13 -0
- rucio/tests/common.py +280 -0
- rucio/tests/common_server.py +149 -0
- rucio/transfertool/__init__.py +13 -0
- rucio/transfertool/bittorrent.py +200 -0
- rucio/transfertool/bittorrent_driver.py +50 -0
- rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
- rucio/transfertool/fts3.py +1600 -0
- rucio/transfertool/fts3_plugins.py +152 -0
- rucio/transfertool/globus.py +201 -0
- rucio/transfertool/globus_library.py +181 -0
- rucio/transfertool/mock.py +89 -0
- rucio/transfertool/transfertool.py +221 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +45 -0
- rucio/web/__init__.py +13 -0
- rucio/web/rest/__init__.py +13 -0
- rucio/web/rest/flaskapi/__init__.py +13 -0
- rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
- rucio/web/rest/flaskapi/v1/__init__.py +13 -0
- rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
- rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
- rucio/web/rest/flaskapi/v1/archives.py +102 -0
- rucio/web/rest/flaskapi/v1/auth.py +1644 -0
- rucio/web/rest/flaskapi/v1/common.py +426 -0
- rucio/web/rest/flaskapi/v1/config.py +304 -0
- rucio/web/rest/flaskapi/v1/credentials.py +213 -0
- rucio/web/rest/flaskapi/v1/dids.py +2340 -0
- rucio/web/rest/flaskapi/v1/dirac.py +116 -0
- rucio/web/rest/flaskapi/v1/export.py +75 -0
- rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
- rucio/web/rest/flaskapi/v1/identities.py +285 -0
- rucio/web/rest/flaskapi/v1/import.py +132 -0
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
- rucio/web/rest/flaskapi/v1/locks.py +358 -0
- rucio/web/rest/flaskapi/v1/main.py +91 -0
- rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
- rucio/web/rest/flaskapi/v1/metrics.py +36 -0
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
- rucio/web/rest/flaskapi/v1/ping.py +88 -0
- rucio/web/rest/flaskapi/v1/redirect.py +366 -0
- rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
- rucio/web/rest/flaskapi/v1/requests.py +998 -0
- rucio/web/rest/flaskapi/v1/rses.py +2250 -0
- rucio/web/rest/flaskapi/v1/rules.py +854 -0
- rucio/web/rest/flaskapi/v1/scopes.py +159 -0
- rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
- rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
- rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
- rucio/web/rest/flaskapi/v1/traces.py +137 -0
- rucio/web/rest/flaskapi/v1/types.py +20 -0
- rucio/web/rest/flaskapi/v1/vos.py +278 -0
- rucio/web/rest/main.py +18 -0
- rucio/web/rest/metrics.py +27 -0
- rucio/web/rest/ping.py +27 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
- rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
- rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
- rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
- rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
- rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
- rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
- rucio-37.0.0rc1.data/scripts/rucio +133 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
- rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
- rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
- rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
- rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
- rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
- rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
- rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
- rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
- rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
- rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
- rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
- rucio-37.0.0rc1.dist-info/METADATA +92 -0
- rucio-37.0.0rc1.dist-info/RECORD +487 -0
- rucio-37.0.0rc1.dist-info/WHEEL +5 -0
- rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
- rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
- 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]
|