rucio 32.8.6__py3-none-any.whl → 35.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio might be problematic. Click here for more details.
- rucio/__init__.py +0 -1
- rucio/alembicrevision.py +1 -2
- rucio/client/__init__.py +0 -1
- rucio/client/accountclient.py +45 -25
- rucio/client/accountlimitclient.py +37 -9
- rucio/client/baseclient.py +199 -154
- rucio/client/client.py +2 -3
- rucio/client/configclient.py +19 -6
- rucio/client/credentialclient.py +9 -4
- rucio/client/didclient.py +238 -63
- rucio/client/diracclient.py +13 -5
- rucio/client/downloadclient.py +162 -51
- rucio/client/exportclient.py +4 -4
- rucio/client/fileclient.py +3 -4
- rucio/client/importclient.py +4 -4
- rucio/client/lifetimeclient.py +21 -5
- rucio/client/lockclient.py +18 -8
- rucio/client/{metaclient.py → metaconventionsclient.py} +18 -15
- rucio/client/pingclient.py +0 -1
- rucio/client/replicaclient.py +15 -5
- rucio/client/requestclient.py +35 -19
- rucio/client/rseclient.py +133 -51
- rucio/client/ruleclient.py +29 -22
- rucio/client/scopeclient.py +8 -6
- rucio/client/subscriptionclient.py +47 -35
- rucio/client/touchclient.py +8 -4
- rucio/client/uploadclient.py +166 -82
- rucio/common/__init__.py +0 -1
- rucio/common/cache.py +4 -4
- rucio/common/config.py +52 -47
- rucio/common/constants.py +69 -2
- rucio/common/constraints.py +0 -1
- rucio/common/didtype.py +24 -22
- rucio/common/dumper/__init__.py +70 -41
- rucio/common/dumper/consistency.py +26 -22
- rucio/common/dumper/data_models.py +16 -23
- rucio/common/dumper/path_parsing.py +0 -1
- rucio/common/exception.py +281 -222
- rucio/common/extra.py +0 -1
- rucio/common/logging.py +54 -38
- rucio/common/pcache.py +122 -101
- rucio/common/plugins.py +153 -0
- rucio/common/policy.py +4 -4
- rucio/common/schema/__init__.py +17 -10
- rucio/common/schema/atlas.py +7 -5
- rucio/common/schema/belleii.py +7 -5
- rucio/common/schema/domatpc.py +7 -5
- rucio/common/schema/escape.py +7 -5
- rucio/common/schema/generic.py +8 -6
- rucio/common/schema/generic_multi_vo.py +7 -5
- rucio/common/schema/icecube.py +7 -5
- rucio/common/stomp_utils.py +0 -1
- rucio/common/stopwatch.py +0 -1
- rucio/common/test_rucio_server.py +2 -2
- rucio/common/types.py +262 -17
- rucio/common/utils.py +743 -451
- rucio/core/__init__.py +0 -1
- rucio/core/account.py +99 -29
- rucio/core/account_counter.py +89 -24
- rucio/core/account_limit.py +90 -24
- rucio/core/authentication.py +86 -29
- rucio/core/config.py +108 -38
- rucio/core/credential.py +14 -7
- rucio/core/did.py +680 -782
- rucio/core/did_meta_plugins/__init__.py +8 -6
- rucio/core/did_meta_plugins/did_column_meta.py +17 -12
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +60 -11
- rucio/core/did_meta_plugins/filter_engine.py +90 -50
- rucio/core/did_meta_plugins/json_meta.py +41 -16
- rucio/core/did_meta_plugins/mongo_meta.py +25 -8
- rucio/core/did_meta_plugins/postgres_meta.py +3 -4
- rucio/core/dirac.py +46 -17
- rucio/core/distance.py +66 -43
- rucio/core/exporter.py +5 -5
- rucio/core/heartbeat.py +181 -81
- rucio/core/identity.py +22 -12
- rucio/core/importer.py +23 -12
- rucio/core/lifetime_exception.py +32 -32
- rucio/core/lock.py +244 -142
- rucio/core/message.py +79 -38
- rucio/core/{meta.py → meta_conventions.py} +57 -44
- rucio/core/monitor.py +19 -13
- rucio/core/naming_convention.py +68 -27
- rucio/core/nongrid_trace.py +17 -5
- rucio/core/oidc.py +151 -29
- rucio/core/permission/__init__.py +18 -6
- rucio/core/permission/atlas.py +50 -35
- rucio/core/permission/belleii.py +6 -5
- rucio/core/permission/escape.py +8 -6
- rucio/core/permission/generic.py +82 -80
- rucio/core/permission/generic_multi_vo.py +9 -7
- rucio/core/quarantined_replica.py +91 -58
- rucio/core/replica.py +1303 -772
- rucio/core/replica_sorter.py +10 -12
- rucio/core/request.py +1133 -285
- rucio/core/rse.py +142 -102
- rucio/core/rse_counter.py +49 -18
- rucio/core/rse_expression_parser.py +6 -7
- rucio/core/rse_selector.py +41 -16
- rucio/core/rule.py +1538 -474
- rucio/core/rule_grouping.py +213 -68
- rucio/core/scope.py +50 -22
- rucio/core/subscription.py +92 -44
- rucio/core/topology.py +66 -24
- rucio/core/trace.py +42 -28
- rucio/core/transfer.py +543 -259
- rucio/core/vo.py +36 -18
- rucio/core/volatile_replica.py +59 -32
- rucio/daemons/__init__.py +0 -1
- rucio/daemons/abacus/__init__.py +0 -1
- rucio/daemons/abacus/account.py +29 -19
- rucio/daemons/abacus/collection_replica.py +21 -10
- rucio/daemons/abacus/rse.py +22 -12
- rucio/daemons/atropos/__init__.py +0 -1
- rucio/daemons/atropos/atropos.py +1 -2
- rucio/daemons/auditor/__init__.py +56 -28
- rucio/daemons/auditor/hdfs.py +17 -6
- rucio/daemons/auditor/srmdumps.py +116 -45
- rucio/daemons/automatix/__init__.py +0 -1
- rucio/daemons/automatix/automatix.py +30 -18
- rucio/daemons/badreplicas/__init__.py +0 -1
- rucio/daemons/badreplicas/minos.py +29 -18
- rucio/daemons/badreplicas/minos_temporary_expiration.py +5 -7
- rucio/daemons/badreplicas/necromancer.py +9 -13
- rucio/daemons/bb8/__init__.py +0 -1
- rucio/daemons/bb8/bb8.py +10 -13
- rucio/daemons/bb8/common.py +151 -154
- rucio/daemons/bb8/nuclei_background_rebalance.py +15 -9
- rucio/daemons/bb8/t2_background_rebalance.py +15 -8
- rucio/daemons/c3po/__init__.py +0 -1
- rucio/daemons/c3po/algorithms/__init__.py +0 -1
- rucio/daemons/c3po/algorithms/simple.py +8 -5
- rucio/daemons/c3po/algorithms/t2_free_space.py +10 -7
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +10 -7
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +30 -15
- rucio/daemons/c3po/c3po.py +81 -52
- rucio/daemons/c3po/collectors/__init__.py +0 -1
- rucio/daemons/c3po/collectors/agis.py +17 -17
- rucio/daemons/c3po/collectors/free_space.py +32 -13
- rucio/daemons/c3po/collectors/jedi_did.py +14 -5
- rucio/daemons/c3po/collectors/mock_did.py +11 -6
- rucio/daemons/c3po/collectors/network_metrics.py +12 -4
- rucio/daemons/c3po/collectors/workload.py +21 -19
- rucio/daemons/c3po/utils/__init__.py +0 -1
- rucio/daemons/c3po/utils/dataset_cache.py +15 -5
- rucio/daemons/c3po/utils/expiring_dataset_cache.py +16 -5
- rucio/daemons/c3po/utils/expiring_list.py +6 -7
- rucio/daemons/c3po/utils/popularity.py +5 -2
- rucio/daemons/c3po/utils/timeseries.py +25 -12
- rucio/daemons/cache/__init__.py +0 -1
- rucio/daemons/cache/consumer.py +21 -15
- rucio/daemons/common.py +42 -18
- rucio/daemons/conveyor/__init__.py +0 -1
- rucio/daemons/conveyor/common.py +69 -37
- rucio/daemons/conveyor/finisher.py +83 -46
- rucio/daemons/conveyor/poller.py +101 -69
- rucio/daemons/conveyor/preparer.py +35 -28
- rucio/daemons/conveyor/receiver.py +64 -21
- rucio/daemons/conveyor/stager.py +33 -28
- rucio/daemons/conveyor/submitter.py +71 -47
- rucio/daemons/conveyor/throttler.py +99 -35
- rucio/daemons/follower/__init__.py +0 -1
- rucio/daemons/follower/follower.py +12 -8
- rucio/daemons/hermes/__init__.py +0 -1
- rucio/daemons/hermes/hermes.py +57 -21
- rucio/daemons/judge/__init__.py +0 -1
- rucio/daemons/judge/cleaner.py +27 -17
- rucio/daemons/judge/evaluator.py +31 -18
- rucio/daemons/judge/injector.py +31 -23
- rucio/daemons/judge/repairer.py +28 -18
- rucio/daemons/oauthmanager/__init__.py +0 -1
- rucio/daemons/oauthmanager/oauthmanager.py +7 -8
- rucio/daemons/reaper/__init__.py +0 -1
- rucio/daemons/reaper/dark_reaper.py +15 -9
- rucio/daemons/reaper/reaper.py +109 -67
- rucio/daemons/replicarecoverer/__init__.py +0 -1
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +255 -116
- rucio/{api → daemons/rsedecommissioner}/__init__.py +0 -1
- 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 +451 -0
- rucio/daemons/rsedecommissioner/profiles/types.py +92 -0
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
- rucio/daemons/storage/__init__.py +0 -1
- rucio/daemons/storage/consistency/__init__.py +0 -1
- rucio/daemons/storage/consistency/actions.py +152 -59
- rucio/daemons/tracer/__init__.py +0 -1
- rucio/daemons/tracer/kronos.py +47 -24
- rucio/daemons/transmogrifier/__init__.py +0 -1
- rucio/daemons/transmogrifier/transmogrifier.py +35 -26
- rucio/daemons/undertaker/__init__.py +0 -1
- rucio/daemons/undertaker/undertaker.py +10 -10
- rucio/db/__init__.py +0 -1
- rucio/db/sqla/__init__.py +16 -2
- rucio/db/sqla/constants.py +10 -1
- rucio/db/sqla/migrate_repo/__init__.py +0 -1
- rucio/db/sqla/migrate_repo/env.py +0 -1
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +0 -1
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +0 -3
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +0 -3
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +1 -3
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +0 -3
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +1 -4
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +0 -1
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +0 -2
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +0 -1
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +0 -1
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +0 -2
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +0 -1
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +0 -1
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +0 -3
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +0 -1
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +1 -2
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +0 -1
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +0 -3
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +1 -4
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +0 -2
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +0 -3
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +0 -3
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +1 -2
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +0 -1
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +0 -1
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +0 -2
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +0 -3
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +0 -2
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +1 -4
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +0 -3
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +1 -4
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +0 -1
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +1 -3
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +0 -2
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +1 -3
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +1 -2
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +1 -3
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +0 -2
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +2 -3
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +0 -3
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +1 -4
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +0 -1
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +0 -1
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +0 -3
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +0 -1
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +0 -2
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +0 -3
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +0 -2
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +2 -4
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +0 -2
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +1 -4
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +0 -3
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +0 -3
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +0 -2
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +0 -3
- 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 +0 -2
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +0 -2
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +0 -3
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +0 -3
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +0 -3
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +1 -2
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +0 -3
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +1 -3
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +1 -3
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +1 -2
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +0 -3
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +0 -3
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +1 -2
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +2 -4
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +0 -1
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +1 -4
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +0 -2
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +0 -3
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +1 -2
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +0 -3
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +1 -3
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +0 -3
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +0 -1
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +1 -2
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +0 -2
- 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 +1 -3
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +0 -2
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +1 -4
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +0 -1
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +0 -1
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +1 -4
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +0 -1
- 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 +0 -3
- 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 +1 -2
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +1 -3
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +0 -3
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +1 -5
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +1 -3
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +0 -3
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +1 -3
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +1 -2
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +0 -3
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +1 -4
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +1 -2
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +1 -4
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +1 -3
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +1 -4
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +0 -2
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +1 -3
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +0 -3
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +1 -3
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +0 -1
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +1 -2
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +1 -3
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +0 -2
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +0 -1
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +1 -2
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +0 -3
- rucio/db/sqla/models.py +122 -216
- rucio/db/sqla/sautils.py +12 -5
- rucio/db/sqla/session.py +71 -43
- rucio/db/sqla/types.py +3 -4
- rucio/db/sqla/util.py +91 -69
- rucio/gateway/__init__.py +13 -0
- rucio/{api → gateway}/account.py +119 -46
- rucio/{api → gateway}/account_limit.py +12 -13
- rucio/{api → gateway}/authentication.py +106 -33
- rucio/{api → gateway}/config.py +12 -13
- rucio/{api → gateway}/credential.py +15 -4
- rucio/{api → gateway}/did.py +384 -140
- rucio/{api → gateway}/dirac.py +16 -6
- rucio/{api → gateway}/exporter.py +3 -4
- rucio/{api → gateway}/heartbeat.py +17 -5
- rucio/{api → gateway}/identity.py +63 -19
- rucio/{api → gateway}/importer.py +3 -4
- rucio/{api → gateway}/lifetime_exception.py +35 -10
- rucio/{api → gateway}/lock.py +34 -12
- rucio/{api/meta.py → gateway/meta_conventions.py} +18 -16
- rucio/{api → gateway}/permission.py +4 -5
- rucio/{api → gateway}/quarantined_replica.py +13 -4
- rucio/{api → gateway}/replica.py +12 -11
- rucio/{api → gateway}/request.py +129 -28
- rucio/{api → gateway}/rse.py +11 -12
- rucio/{api → gateway}/rule.py +117 -35
- rucio/{api → gateway}/scope.py +24 -14
- rucio/{api → gateway}/subscription.py +65 -43
- rucio/{api → gateway}/vo.py +17 -7
- rucio/rse/__init__.py +3 -4
- rucio/rse/protocols/__init__.py +0 -1
- rucio/rse/protocols/bittorrent.py +184 -0
- rucio/rse/protocols/cache.py +1 -2
- rucio/rse/protocols/dummy.py +1 -2
- rucio/rse/protocols/gfal.py +12 -10
- rucio/rse/protocols/globus.py +7 -7
- rucio/rse/protocols/gsiftp.py +2 -3
- rucio/rse/protocols/http_cache.py +1 -2
- rucio/rse/protocols/mock.py +1 -2
- rucio/rse/protocols/ngarc.py +1 -2
- rucio/rse/protocols/posix.py +12 -13
- rucio/rse/protocols/protocol.py +116 -52
- rucio/rse/protocols/rclone.py +6 -7
- rucio/rse/protocols/rfio.py +4 -5
- rucio/rse/protocols/srm.py +9 -10
- rucio/rse/protocols/ssh.py +8 -9
- rucio/rse/protocols/storm.py +2 -3
- rucio/rse/protocols/webdav.py +17 -14
- rucio/rse/protocols/xrootd.py +23 -17
- rucio/rse/rsemanager.py +19 -7
- rucio/tests/__init__.py +0 -1
- rucio/tests/common.py +43 -17
- rucio/tests/common_server.py +3 -3
- rucio/transfertool/__init__.py +0 -1
- rucio/transfertool/bittorrent.py +199 -0
- rucio/transfertool/bittorrent_driver.py +52 -0
- rucio/transfertool/bittorrent_driver_qbittorrent.py +133 -0
- rucio/transfertool/fts3.py +250 -138
- rucio/transfertool/fts3_plugins.py +152 -0
- rucio/transfertool/globus.py +9 -8
- rucio/transfertool/globus_library.py +1 -2
- rucio/transfertool/mock.py +21 -12
- rucio/transfertool/transfertool.py +33 -24
- rucio/vcsversion.py +4 -4
- rucio/version.py +5 -13
- rucio/web/__init__.py +0 -1
- rucio/web/rest/__init__.py +0 -1
- rucio/web/rest/flaskapi/__init__.py +0 -1
- rucio/web/rest/flaskapi/authenticated_bp.py +0 -1
- rucio/web/rest/flaskapi/v1/__init__.py +0 -1
- rucio/web/rest/flaskapi/v1/accountlimits.py +15 -13
- rucio/web/rest/flaskapi/v1/accounts.py +49 -48
- rucio/web/rest/flaskapi/v1/archives.py +12 -10
- rucio/web/rest/flaskapi/v1/auth.py +146 -144
- rucio/web/rest/flaskapi/v1/common.py +82 -41
- rucio/web/rest/flaskapi/v1/config.py +5 -6
- rucio/web/rest/flaskapi/v1/credentials.py +7 -8
- rucio/web/rest/flaskapi/v1/dids.py +158 -28
- rucio/web/rest/flaskapi/v1/dirac.py +8 -8
- rucio/web/rest/flaskapi/v1/export.py +3 -5
- rucio/web/rest/flaskapi/v1/heartbeats.py +3 -5
- rucio/web/rest/flaskapi/v1/identities.py +3 -5
- rucio/web/rest/flaskapi/v1/import.py +3 -4
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +6 -9
- rucio/web/rest/flaskapi/v1/locks.py +2 -4
- rucio/web/rest/flaskapi/v1/main.py +10 -2
- rucio/web/rest/flaskapi/v1/{meta.py → meta_conventions.py} +26 -11
- rucio/web/rest/flaskapi/v1/metrics.py +1 -2
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +4 -4
- rucio/web/rest/flaskapi/v1/ping.py +6 -7
- rucio/web/rest/flaskapi/v1/redirect.py +8 -9
- rucio/web/rest/flaskapi/v1/replicas.py +43 -19
- rucio/web/rest/flaskapi/v1/requests.py +178 -21
- rucio/web/rest/flaskapi/v1/rses.py +61 -26
- rucio/web/rest/flaskapi/v1/rules.py +48 -18
- rucio/web/rest/flaskapi/v1/scopes.py +3 -5
- rucio/web/rest/flaskapi/v1/subscriptions.py +22 -18
- rucio/web/rest/flaskapi/v1/traces.py +4 -4
- rucio/web/rest/flaskapi/v1/types.py +20 -0
- rucio/web/rest/flaskapi/v1/vos.py +3 -5
- rucio/web/rest/main.py +0 -1
- rucio/web/rest/metrics.py +0 -1
- rucio/web/rest/ping.py +27 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/ldap.cfg.template +1 -1
- rucio-35.8.0.data/data/rucio/requirements.server.txt +268 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/tools/bootstrap.py +3 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/tools/merge_rucio_configs.py +2 -5
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/tools/reset_database.py +3 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio +87 -85
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-abacus-account +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-abacus-collection-replica +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-abacus-rse +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-admin +45 -32
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-atropos +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-auditor +13 -7
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-automatix +1 -2
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-bb8 +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-c3po +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-cache-client +2 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-cache-consumer +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-finisher +1 -2
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-poller +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-preparer +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-receiver +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-stager +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-submitter +2 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-conveyor-throttler +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-dark-reaper +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-dumper +11 -10
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-follower +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-hermes +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-judge-cleaner +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-judge-evaluator +2 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-judge-injector +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-judge-repairer +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-kronos +1 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-minos +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-minos-temporary-expiration +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-necromancer +1 -2
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-oauth-manager +2 -3
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-reaper +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-replica-recoverer +6 -7
- rucio-35.8.0.data/scripts/rucio-rse-decommissioner +66 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-storage-consistency-actions +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-transmogrifier +0 -1
- {rucio-32.8.6.data → rucio-35.8.0.data}/scripts/rucio-undertaker +1 -2
- rucio-35.8.0.dist-info/METADATA +72 -0
- rucio-35.8.0.dist-info/RECORD +493 -0
- {rucio-32.8.6.dist-info → rucio-35.8.0.dist-info}/WHEEL +1 -1
- {rucio-32.8.6.dist-info → rucio-35.8.0.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/api/temporary_did.py +0 -49
- rucio/common/schema/cms.py +0 -478
- rucio/common/schema/lsst.py +0 -423
- rucio/core/permission/cms.py +0 -1166
- rucio/core/temporary_did.py +0 -188
- rucio/daemons/reaper/light_reaper.py +0 -255
- rucio/web/rest/flaskapi/v1/tmp_dids.py +0 -115
- rucio-32.8.6.data/data/rucio/requirements.txt +0 -55
- rucio-32.8.6.data/scripts/rucio-light-reaper +0 -53
- rucio-32.8.6.dist-info/METADATA +0 -83
- rucio-32.8.6.dist-info/RECORD +0 -481
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
- {rucio-32.8.6.data → rucio-35.8.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
- {rucio-32.8.6.dist-info → rucio-35.8.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-32.8.6.dist-info → rucio-35.8.0.dist-info}/top_level.txt +0 -0
rucio/transfertool/fts3.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
3
2
|
#
|
|
4
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -19,10 +18,9 @@ import logging
|
|
|
19
18
|
import pathlib
|
|
20
19
|
import traceback
|
|
21
20
|
import uuid
|
|
22
|
-
from collections.abc import Callable
|
|
23
21
|
from configparser import NoOptionError, NoSectionError
|
|
24
22
|
from json import loads
|
|
25
|
-
from typing import Any, Optional,
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
26
24
|
from urllib.parse import urlparse
|
|
27
25
|
|
|
28
26
|
import requests
|
|
@@ -31,20 +29,26 @@ from requests.adapters import ReadTimeout
|
|
|
31
29
|
from requests.packages.urllib3 import disable_warnings # pylint: disable=import-error
|
|
32
30
|
|
|
33
31
|
from rucio.common.cache import make_region_memcached
|
|
34
|
-
from rucio.common.config import config_get, config_get_bool, config_get_int
|
|
35
|
-
from rucio.common.constants import FTS_JOB_TYPE, FTS_STATE,
|
|
36
|
-
from rucio.common.exception import TransferToolTimeout, TransferToolWrongAnswer
|
|
32
|
+
from rucio.common.config import config_get, config_get_bool, config_get_int, config_get_list
|
|
33
|
+
from rucio.common.constants import FTS_COMPLETE_STATE, FTS_JOB_TYPE, FTS_STATE, RseAttr
|
|
34
|
+
from rucio.common.exception import DuplicateFileTransferSubmission, TransferToolTimeout, TransferToolWrongAnswer
|
|
37
35
|
from rucio.common.stopwatch import Stopwatch
|
|
38
|
-
from rucio.common.utils import APIEncoder, chunks,
|
|
36
|
+
from rucio.common.utils import PREFERRED_CHECKSUM, APIEncoder, chunks, deep_merge_dict
|
|
39
37
|
from rucio.core.monitor import MetricManager
|
|
40
|
-
from rucio.core.oidc import
|
|
38
|
+
from rucio.core.oidc import request_token
|
|
41
39
|
from rucio.core.request import get_source_rse, get_transfer_error
|
|
42
|
-
from rucio.core.rse import get_rse_supported_checksums_from_attributes
|
|
40
|
+
from rucio.core.rse import determine_audience_for_rse, determine_scope_for_rse, get_rse_supported_checksums_from_attributes
|
|
43
41
|
from rucio.db.sqla.constants import RequestState
|
|
44
|
-
from rucio.transfertool.
|
|
42
|
+
from rucio.transfertool.fts3_plugins import FTS3TapeMetadataPlugin
|
|
43
|
+
from rucio.transfertool.transfertool import TransferStatusReport, Transfertool, TransferToolBuilder
|
|
45
44
|
|
|
46
45
|
if TYPE_CHECKING:
|
|
47
|
-
from
|
|
46
|
+
from collections.abc import Iterable, Sequence
|
|
47
|
+
|
|
48
|
+
from sqlalchemy.orm import Session
|
|
49
|
+
|
|
50
|
+
from rucio.common.types import LoggerFunction
|
|
51
|
+
from rucio.core.request import DirectTransfer
|
|
48
52
|
from rucio.core.rse import RseData
|
|
49
53
|
|
|
50
54
|
logging.getLogger("requests").setLevel(logging.CRITICAL)
|
|
@@ -70,9 +74,6 @@ BULK_QUERY_COUNTER = METRICS.counter(name='{host}.bulk_query.{state}',
|
|
|
70
74
|
QUERY_DETAILS_COUNTER = METRICS.counter(name='{host}.query_details.{state}',
|
|
71
75
|
documentation='Number of detailed status queries', labelnames=('state', 'host'))
|
|
72
76
|
|
|
73
|
-
ALLOW_USER_OIDC_TOKENS = config_get_bool('conveyor', 'allow_user_oidc_tokens', False, False)
|
|
74
|
-
REQUEST_OIDC_SCOPE = config_get('conveyor', 'request_oidc_scope', False, 'fts:submit-transfer')
|
|
75
|
-
REQUEST_OIDC_AUDIENCE = config_get('conveyor', 'request_oidc_audience', False, 'fts:example')
|
|
76
77
|
REWRITE_HTTPS_TO_DAVS = config_get_bool('transfers', 'rewrite_https_to_davs', default=False)
|
|
77
78
|
VO_CERTS_PATH = config_get('conveyor', 'vo_certs_path', False, None)
|
|
78
79
|
|
|
@@ -109,7 +110,7 @@ _SCITAGS_EXP_ID = None
|
|
|
109
110
|
_SCITAGS_ACTIVITY_IDS = {}
|
|
110
111
|
|
|
111
112
|
|
|
112
|
-
def _scitags_ids(logger:
|
|
113
|
+
def _scitags_ids(logger: "LoggerFunction" = logging.log) -> "tuple[int | None, dict[str, int]]":
|
|
113
114
|
"""
|
|
114
115
|
Re-fetch if needed and return the scitags ids
|
|
115
116
|
"""
|
|
@@ -124,7 +125,7 @@ def _scitags_ids(logger: Callable[..., Any] = logging.log) -> "tuple[int | None,
|
|
|
124
125
|
if _SCITAGS_NEXT_REFRESH < now:
|
|
125
126
|
exp_name = config_get('packet-marking', 'exp_name', default='')
|
|
126
127
|
fetch_url = config_get('packet-marking', 'fetch_url', default='https://www.scitags.org/api.json')
|
|
127
|
-
fetch_interval = config_get_int('packet-marking', 'fetch_interval', default=datetime.timedelta(hours=48).
|
|
128
|
+
fetch_interval = config_get_int('packet-marking', 'fetch_interval', default=int(datetime.timedelta(hours=48).total_seconds()))
|
|
128
129
|
fetch_timeout = config_get_int('packet-marking', 'fetch_timeout', default=5)
|
|
129
130
|
|
|
130
131
|
_SCITAGS_NEXT_REFRESH = now + datetime.timedelta(seconds=fetch_interval)
|
|
@@ -160,7 +161,7 @@ def _scitags_ids(logger: Callable[..., Any] = logging.log) -> "tuple[int | None,
|
|
|
160
161
|
return _SCITAGS_EXP_ID, _SCITAGS_ACTIVITY_IDS
|
|
161
162
|
|
|
162
163
|
|
|
163
|
-
def _pick_cert_file(vo:
|
|
164
|
+
def _pick_cert_file(vo: Optional[str]) -> Optional[str]:
|
|
164
165
|
cert = None
|
|
165
166
|
if vo:
|
|
166
167
|
vo_cert = config_get('vo_certs', vo, False, None)
|
|
@@ -177,7 +178,7 @@ def _pick_cert_file(vo: "Optional[str]") -> "Optional[str]":
|
|
|
177
178
|
return cert
|
|
178
179
|
|
|
179
180
|
|
|
180
|
-
def _configured_source_strategy(activity: str, logger:
|
|
181
|
+
def _configured_source_strategy(activity: str, logger: "LoggerFunction") -> str:
|
|
181
182
|
"""
|
|
182
183
|
Retrieve from the configuration the source selection strategy for the given activity
|
|
183
184
|
"""
|
|
@@ -198,36 +199,20 @@ def _configured_source_strategy(activity: str, logger: Callable[..., Any]) -> st
|
|
|
198
199
|
return activity_source_strategy.get(str(activity), default_source_strategy)
|
|
199
200
|
|
|
200
201
|
|
|
201
|
-
def oidc_supported(transfer_hop) -> bool:
|
|
202
|
-
"""
|
|
203
|
-
checking OIDC AuthN/Z support per destination and source RSEs;
|
|
204
|
-
|
|
205
|
-
for oidc_support to be activated, all sources and the destination must explicitly support it
|
|
206
|
-
"""
|
|
207
|
-
# assumes use of boolean 'oidc_support' RSE attribute
|
|
208
|
-
if not transfer_hop.dst.rse.attributes.get('oidc_support', False):
|
|
209
|
-
return False
|
|
210
|
-
|
|
211
|
-
for source in transfer_hop.sources:
|
|
212
|
-
if not source.rse.attributes.get('oidc_support', False):
|
|
213
|
-
return False
|
|
214
|
-
return True
|
|
215
|
-
|
|
216
|
-
|
|
217
202
|
def _available_checksums(
|
|
218
|
-
transfer: "
|
|
203
|
+
transfer: "DirectTransfer",
|
|
219
204
|
) -> tuple[set[str], set[str]]:
|
|
220
205
|
"""
|
|
221
206
|
Get checksums which can be used for file validation on the source and the destination RSE
|
|
222
207
|
"""
|
|
223
208
|
src_attributes = transfer.src.rse.attributes
|
|
224
|
-
if src_attributes.get(
|
|
209
|
+
if src_attributes.get(RseAttr.VERIFY_CHECKSUM, True):
|
|
225
210
|
src_checksums = set(get_rse_supported_checksums_from_attributes(src_attributes))
|
|
226
211
|
else:
|
|
227
212
|
src_checksums = set()
|
|
228
213
|
|
|
229
214
|
dst_attributes = transfer.dst.rse.attributes
|
|
230
|
-
if dst_attributes.get(
|
|
215
|
+
if dst_attributes.get(RseAttr.VERIFY_CHECKSUM, True):
|
|
231
216
|
dst_checksums = set(get_rse_supported_checksums_from_attributes(dst_attributes))
|
|
232
217
|
else:
|
|
233
218
|
dst_checksums = set()
|
|
@@ -236,8 +221,8 @@ def _available_checksums(
|
|
|
236
221
|
|
|
237
222
|
|
|
238
223
|
def _hop_checksum_validation_strategy(
|
|
239
|
-
transfer: "
|
|
240
|
-
logger:
|
|
224
|
+
transfer: "DirectTransfer",
|
|
225
|
+
logger: "LoggerFunction",
|
|
241
226
|
) -> tuple[str, set[str]]:
|
|
242
227
|
"""
|
|
243
228
|
Compute the checksum validation strategy (none, source, destination or both) depending
|
|
@@ -262,8 +247,8 @@ def _hop_checksum_validation_strategy(
|
|
|
262
247
|
|
|
263
248
|
|
|
264
249
|
def _path_checksum_validation_strategy(
|
|
265
|
-
transfer_path: "list[
|
|
266
|
-
logger:
|
|
250
|
+
transfer_path: "list[DirectTransfer]",
|
|
251
|
+
logger: "LoggerFunction",
|
|
267
252
|
) -> str:
|
|
268
253
|
"""
|
|
269
254
|
Compute the checksum validation strategy for the whole transfer path.
|
|
@@ -279,7 +264,7 @@ def _path_checksum_validation_strategy(
|
|
|
279
264
|
|
|
280
265
|
|
|
281
266
|
def _pick_fts_checksum(
|
|
282
|
-
transfer: "
|
|
267
|
+
transfer: "DirectTransfer",
|
|
283
268
|
path_strategy: "str",
|
|
284
269
|
) -> Optional[str]:
|
|
285
270
|
"""
|
|
@@ -313,9 +298,31 @@ def _pick_fts_checksum(
|
|
|
313
298
|
return checksum_to_use
|
|
314
299
|
|
|
315
300
|
|
|
316
|
-
def
|
|
301
|
+
def _use_tokens(transfer_hop: "DirectTransfer"):
|
|
302
|
+
"""Whether a transfer can be performed with tokens.
|
|
303
|
+
|
|
304
|
+
In order to be so, all the involved RSEs must have it explicitly enabled
|
|
305
|
+
and the protocol being used must be WebDAV.
|
|
306
|
+
"""
|
|
307
|
+
for endpoint in [*transfer_hop.sources, transfer_hop.dst]:
|
|
308
|
+
if (endpoint.rse.attributes.get(RseAttr.OIDC_SUPPORT) is not True
|
|
309
|
+
or endpoint.scheme != 'davs'):
|
|
310
|
+
return False
|
|
311
|
+
return True
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def build_job_params(
|
|
315
|
+
transfer_path: list["DirectTransfer"],
|
|
316
|
+
bring_online: Optional[int] = None,
|
|
317
|
+
default_lifetime: Optional[int] = None,
|
|
318
|
+
archive_timeout_override: Optional[int] = None,
|
|
319
|
+
max_time_in_queue: Optional[dict] = None,
|
|
320
|
+
logger: "LoggerFunction" = logging.log
|
|
321
|
+
) -> dict[str, Any]:
|
|
317
322
|
"""
|
|
318
323
|
Prepare the job parameters which will be passed to FTS transfertool
|
|
324
|
+
Please refer to https://fts3-docs.web.cern.ch/fts3-docs/fts-rest/docs/bulk.html#parameters
|
|
325
|
+
for the list of parameters.
|
|
319
326
|
"""
|
|
320
327
|
|
|
321
328
|
# The last hop is the main request (the one which triggered the whole transfer),
|
|
@@ -323,13 +330,46 @@ def build_job_params(transfer_path, bring_online, default_lifetime, archive_time
|
|
|
323
330
|
last_hop = transfer_path[-1]
|
|
324
331
|
first_hop = transfer_path[0]
|
|
325
332
|
|
|
326
|
-
|
|
333
|
+
# Overwriting by default is set to True for non TAPE RSEs.
|
|
334
|
+
# Tape RSEs can force overwrite by setting the "overwrite" attribute to True.
|
|
335
|
+
# There is yet another configuration option: transfers->overwrite_corrupted_files that when is set to True
|
|
336
|
+
# it will retry failed requests with overwrite flag set to True
|
|
337
|
+
overwrite, overwrite_when_only_on_disk, bring_online_local = True, False, None
|
|
338
|
+
|
|
327
339
|
if first_hop.src.rse.is_tape_or_staging_required():
|
|
328
340
|
# Activate bring_online if it was requested by first hop
|
|
329
341
|
# We don't allow multihop via a tape, so bring_online should not be set on any other hop
|
|
330
342
|
bring_online_local = bring_online
|
|
343
|
+
|
|
331
344
|
if last_hop.dst.rse.is_tape():
|
|
332
|
-
|
|
345
|
+
# FTS v3.12.12 introduced a new boolean parameter "overwrite_when_only_on_disk" that controls if the file can be overwritten
|
|
346
|
+
# in TAPE enabled RSEs ONLY IF the file is on the disk buffer and not yet committed to tape media.
|
|
347
|
+
# This functionality should reduce the number of stuck files in the disk buffer that are not migrated to tape media (for whatever reason).
|
|
348
|
+
# Please be aware that FTS does not guarantee an atomic operation from the time it checks for existence of the file on disk and tape and
|
|
349
|
+
# the moment the file is overwritten, so there is a race condition that could overwrite the file on the tape media
|
|
350
|
+
|
|
351
|
+
# Setting both flags is incompatible, so we opt in for the safest approach: "overwrite_when_only_on_disk"
|
|
352
|
+
# this is aligned with FTS implementation: see (internal access only): https://its.cern.ch/jira/browse/FTS-2007
|
|
353
|
+
|
|
354
|
+
overwrite_when_only_on_disk = last_hop.dst.rse.attributes.get('overwrite_when_only_on_disk', False)
|
|
355
|
+
overwrite = False if overwrite_when_only_on_disk else last_hop.dst.rse.attributes.get('overwrite', False)
|
|
356
|
+
|
|
357
|
+
# We still need to check for the
|
|
358
|
+
# "transfers -> overwrite_corrupted_files setting. The logic behind this flag is that
|
|
359
|
+
# it will update the rws (RequestWithSources) with the "overwrite" attribute set to True
|
|
360
|
+
# after finding an 'Destination file exists and overwrite is not enabled' error message
|
|
361
|
+
overwrite_corrupted_files = last_hop.rws.attributes.get('overwrite', False)
|
|
362
|
+
if overwrite_corrupted_files:
|
|
363
|
+
overwrite = True # both for DISK and TAPE
|
|
364
|
+
|
|
365
|
+
logger(logging.DEBUG, 'RSE:%s Is it Tape? %s overwrite_when_only_on_disk:%s overwrite:%s overwrite_corrupted_files=%s' % (
|
|
366
|
+
last_hop.dst.rse.name,
|
|
367
|
+
last_hop.dst.rse.is_tape(),
|
|
368
|
+
overwrite_when_only_on_disk,
|
|
369
|
+
overwrite,
|
|
370
|
+
overwrite_corrupted_files))
|
|
371
|
+
|
|
372
|
+
logger(logging.DEBUG, 'RSE attributes are: %s' % (last_hop.dst.rse.attributes))
|
|
333
373
|
|
|
334
374
|
# Get dest space token
|
|
335
375
|
dest_protocol = last_hop.protocol_factory.protocol(last_hop.dst.rse, last_hop.dst.scheme, last_hop.operation_dest)
|
|
@@ -338,8 +378,8 @@ def build_job_params(transfer_path, bring_online, default_lifetime, archive_time
|
|
|
338
378
|
dest_protocol.attributes['extended_attributes'] and 'space_token' in dest_protocol.attributes['extended_attributes']:
|
|
339
379
|
dest_spacetoken = dest_protocol.attributes['extended_attributes']['space_token']
|
|
340
380
|
|
|
341
|
-
strict_copy = last_hop.dst.rse.attributes.get(
|
|
342
|
-
archive_timeout = last_hop.dst.rse.attributes.get(
|
|
381
|
+
strict_copy = last_hop.dst.rse.attributes.get(RseAttr.STRICT_COPY, False)
|
|
382
|
+
archive_timeout = last_hop.dst.rse.attributes.get(RseAttr.ARCHIVE_TIMEOUT, None)
|
|
343
383
|
|
|
344
384
|
job_params = {'account': last_hop.rws.account,
|
|
345
385
|
'verify_checksum': _path_checksum_validation_strategy(transfer_path, logger=logger),
|
|
@@ -348,29 +388,32 @@ def build_job_params(transfer_path, bring_online, default_lifetime, archive_time
|
|
|
348
388
|
'job_metadata': {
|
|
349
389
|
'issuer': 'rucio',
|
|
350
390
|
'multi_sources': False,
|
|
391
|
+
'overwrite_when_only_on_disk': overwrite_when_only_on_disk,
|
|
351
392
|
},
|
|
352
|
-
'overwrite':
|
|
393
|
+
'overwrite': overwrite,
|
|
394
|
+
'overwrite_when_only_on_disk': overwrite_when_only_on_disk,
|
|
353
395
|
'priority': last_hop.rws.priority}
|
|
354
396
|
|
|
355
397
|
if len(transfer_path) > 1:
|
|
356
398
|
job_params['multihop'] = True
|
|
357
399
|
job_params['job_metadata']['multihop'] = True
|
|
358
|
-
elif len(last_hop.
|
|
400
|
+
elif len(last_hop.sources) > 1:
|
|
359
401
|
job_params['job_metadata']['multi_sources'] = True
|
|
360
402
|
if strict_copy:
|
|
361
403
|
job_params['strict_copy'] = strict_copy
|
|
362
404
|
if dest_spacetoken:
|
|
363
405
|
job_params['spacetoken'] = dest_spacetoken
|
|
364
|
-
if last_hop.
|
|
406
|
+
if (last_hop.dst.rse.attributes.get(RseAttr.USE_IPV4, False)
|
|
407
|
+
or any(src.rse.attributes.get(RseAttr.USE_IPV4, False) for src in last_hop.sources)):
|
|
365
408
|
job_params['ipv4'] = True
|
|
366
409
|
job_params['ipv6'] = False
|
|
367
410
|
|
|
368
411
|
# assume s3alternate True (path-style URL S3 RSEs)
|
|
369
412
|
job_params['s3alternate'] = True
|
|
370
|
-
src_rse_s3_url_style = first_hop.src.rse.attributes.get(
|
|
413
|
+
src_rse_s3_url_style = first_hop.src.rse.attributes.get(RseAttr.S3_URL_STYLE, None)
|
|
371
414
|
if src_rse_s3_url_style == "host":
|
|
372
415
|
job_params['s3alternate'] = False
|
|
373
|
-
dst_rse_s3_url_style = last_hop.dst.rse.attributes.get(
|
|
416
|
+
dst_rse_s3_url_style = last_hop.dst.rse.attributes.get(RseAttr.S3_URL_STYLE, None)
|
|
374
417
|
if dst_rse_s3_url_style == "host":
|
|
375
418
|
job_params['s3alternate'] = False
|
|
376
419
|
|
|
@@ -393,23 +436,42 @@ def build_job_params(transfer_path, bring_online, default_lifetime, archive_time
|
|
|
393
436
|
elif 'default' in max_time_in_queue:
|
|
394
437
|
job_params['max_time_in_queue'] = max_time_in_queue['default']
|
|
395
438
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
439
|
+
# Refer to https://its.cern.ch/jira/browse/FTS-1749 for full details (login needed), extract below:
|
|
440
|
+
# Why the overwrite_hop parameter is needed?
|
|
441
|
+
# Rucio decides that a multihop transfer is needed DISK1 --> DISK2 --> TAPE1 in order to put the file on tape. For some reason, the file already exists on DISK2.
|
|
442
|
+
# Rucio doesn't know about this file on DISK2. It could be either a correct or corrupted file. This can be due to a previous issue on Rucio side, FTS side, network side, etc (many possible reasons).
|
|
443
|
+
# Normally, Rucio allows overwrite towards any disk destination, but denies overwrite towards a tape destination. However, in this case, because the destination of the multihop is a tape, DISK2 cannot be overwritten.
|
|
444
|
+
# Proposed solution
|
|
445
|
+
# Provide an --overwrite-hop submission option, which instructs FTS to overwrite all transfers except for the destination within a multihop submission.
|
|
446
|
+
|
|
447
|
+
# direct transfers, is not multihop
|
|
448
|
+
if len(transfer_path) == 1:
|
|
449
|
+
overwrite_hop = False
|
|
450
|
+
|
|
451
|
+
else:
|
|
452
|
+
# Set `overwrite_hop` to `True` only if all hops allow it
|
|
453
|
+
overwrite_hop = all(transfer_hop.rws.attributes.get('overwrite', True) for transfer_hop in transfer_path[:-1])
|
|
454
|
+
job_params['overwrite'] = overwrite_hop and job_params['overwrite']
|
|
455
|
+
|
|
403
456
|
if not job_params['overwrite'] and overwrite_hop:
|
|
404
457
|
job_params['overwrite_hop'] = overwrite_hop
|
|
405
458
|
|
|
459
|
+
logger(logging.DEBUG, 'Job parameters are : %s' % (job_params))
|
|
406
460
|
return job_params
|
|
407
461
|
|
|
408
462
|
|
|
409
|
-
def bulk_group_transfers(
|
|
410
|
-
|
|
463
|
+
def bulk_group_transfers(
|
|
464
|
+
transfer_paths: "Iterable[list[DirectTransfer]]",
|
|
465
|
+
policy: str = 'rule',
|
|
466
|
+
group_bulk: int = 200,
|
|
467
|
+
source_strategy: Optional[str] = None,
|
|
468
|
+
max_time_in_queue: Optional[dict] = None,
|
|
469
|
+
logger: "LoggerFunction" = logging.log,
|
|
470
|
+
archive_timeout_override: Optional[int] = None,
|
|
471
|
+
bring_online: Optional[int] = None,
|
|
472
|
+
default_lifetime: Optional[int] = None) -> list[dict[str, Any]]:
|
|
411
473
|
"""
|
|
412
|
-
Group transfers in bulk based on certain
|
|
474
|
+
Group transfers in bulk based on certain criteria
|
|
413
475
|
|
|
414
476
|
:param transfer_paths: List of transfer paths to group. Each path is a list of single-hop transfers.
|
|
415
477
|
:param policy: Policy to use to group.
|
|
@@ -433,6 +495,7 @@ def bulk_group_transfers(transfer_paths, policy='rule', group_bulk=200, source_s
|
|
|
433
495
|
max_time_in_queue=max_time_in_queue,
|
|
434
496
|
logger=logger
|
|
435
497
|
)
|
|
498
|
+
logger(logging.DEBUG, 'bulk_group_transfers: Job parameters are: %s' % (job_params))
|
|
436
499
|
if job_params['job_metadata'].get('multi_sources') or job_params['job_metadata'].get('multihop'):
|
|
437
500
|
# for multi-hop and multi-source transfers, no bulk submission.
|
|
438
501
|
fts_jobs.append({'transfers': transfer_path[0:group_bulk], 'job_params': job_params})
|
|
@@ -478,6 +541,7 @@ def bulk_group_transfers(transfer_paths, policy='rule', group_bulk=200, source_s
|
|
|
478
541
|
# split transfer groups to have at most group_bulk elements in each one
|
|
479
542
|
for group in grouped_transfers.values():
|
|
480
543
|
job_params = group['job_params']
|
|
544
|
+
logger(logging.DEBUG, 'bulk_group_transfers: grouped_transfers.values(): Job parameters are: %s' % (job_params))
|
|
481
545
|
for transfer_paths in chunks(group['transfers'], group_bulk):
|
|
482
546
|
fts_jobs.append({'transfers': transfer_paths, 'job_params': job_params})
|
|
483
547
|
|
|
@@ -498,7 +562,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
498
562
|
'attributes',
|
|
499
563
|
]
|
|
500
564
|
|
|
501
|
-
def __init__(self, external_host, request_id, request=None):
|
|
565
|
+
def __init__(self, external_host: str, request_id: str, request: Optional[dict] = None):
|
|
502
566
|
super().__init__(request_id, request=request)
|
|
503
567
|
self.external_host = external_host
|
|
504
568
|
|
|
@@ -512,7 +576,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
512
576
|
self._reason = None
|
|
513
577
|
self._src_rse = None
|
|
514
578
|
self._fts_address = self.external_host
|
|
515
|
-
# Supported db fields
|
|
579
|
+
# Supported db fields below:
|
|
516
580
|
self.state = None
|
|
517
581
|
self.external_id = None
|
|
518
582
|
self.started_at = None
|
|
@@ -527,10 +591,10 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
527
591
|
return f'Transfer {self._transfer_id} of {self._file_metadata["scope"]}:{self._file_metadata["name"]} ' \
|
|
528
592
|
f'{self._file_metadata["src_rse"]} --({self._file_metadata["request_id"]})-> {self._file_metadata["dst_rse"]}'
|
|
529
593
|
|
|
530
|
-
def initialize(self, session, logger=logging.log):
|
|
594
|
+
def initialize(self, session: "Session", logger: "LoggerFunction" = logging.log) -> None:
|
|
531
595
|
raise NotImplementedError(f"{self.__class__.__name__} is abstract and shouldn't be used directly")
|
|
532
596
|
|
|
533
|
-
def get_monitor_msg_fields(self, session, logger=logging.log):
|
|
597
|
+
def get_monitor_msg_fields(self, session: "Session", logger: "LoggerFunction" = logging.log) -> dict[str, Any]:
|
|
534
598
|
self.ensure_initialized(session, logger)
|
|
535
599
|
fields = {
|
|
536
600
|
'transfer_link': self._transfer_link(),
|
|
@@ -546,10 +610,10 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
546
610
|
}
|
|
547
611
|
return fields
|
|
548
612
|
|
|
549
|
-
def _transfer_link(self):
|
|
613
|
+
def _transfer_link(self) -> str:
|
|
550
614
|
return '%s/fts3/ftsmon/#/job/%s' % (self._fts_address.replace('8446', '8449'), self._transfer_id)
|
|
551
615
|
|
|
552
|
-
def _find_attribute_updates(self, request, new_state, reason, overwrite_corrupted_files):
|
|
616
|
+
def _find_attribute_updates(self, request: dict, new_state: RequestState, reason: str, overwrite_corrupted_files: Optional[bool] = None) -> Optional[dict[str, Any]]:
|
|
553
617
|
attributes = None
|
|
554
618
|
if new_state == RequestState.FAILED and 'Destination file exists and overwrite is not enabled' in (reason or ''):
|
|
555
619
|
dst_file = self._file_metadata.get('dst_file', {})
|
|
@@ -559,7 +623,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
559
623
|
attributes['overwrite'] = True
|
|
560
624
|
return attributes
|
|
561
625
|
|
|
562
|
-
def _find_used_source_rse(self, session, logger):
|
|
626
|
+
def _find_used_source_rse(self, session: "Session", logger: "LoggerFunction") -> tuple[Optional[str], Optional[str]]:
|
|
563
627
|
"""
|
|
564
628
|
For multi-source transfers, FTS has a choice between multiple sources.
|
|
565
629
|
Find which of the possible sources FTS actually used for the transfer.
|
|
@@ -577,7 +641,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
577
641
|
return meta_rse_name, meta_rse_id
|
|
578
642
|
|
|
579
643
|
@staticmethod
|
|
580
|
-
def _dst_file_set_and_file_corrupted(request, dst_file):
|
|
644
|
+
def _dst_file_set_and_file_corrupted(request: dict, dst_file: dict) -> bool:
|
|
581
645
|
"""
|
|
582
646
|
Returns True if the `dst_file` dict returned by fts was filled and its content allows to
|
|
583
647
|
affirm that the file is corrupted.
|
|
@@ -590,7 +654,7 @@ class Fts3TransferStatusReport(TransferStatusReport):
|
|
|
590
654
|
return False
|
|
591
655
|
|
|
592
656
|
@staticmethod
|
|
593
|
-
def _dst_file_set_and_file_correct(request, dst_file):
|
|
657
|
+
def _dst_file_set_and_file_correct(request: dict, dst_file: dict) -> bool:
|
|
594
658
|
"""
|
|
595
659
|
Returns True if the `dst_file` dict returned by fts was filled and its content allows to
|
|
596
660
|
affirm that the file is correct.
|
|
@@ -635,19 +699,21 @@ class FTS3CompletionMessageTransferStatusReport(Fts3TransferStatusReport):
|
|
|
635
699
|
"""
|
|
636
700
|
Parses FTS Completion messages received via the message queue
|
|
637
701
|
"""
|
|
638
|
-
def __init__(self, external_host, request_id, fts_message):
|
|
702
|
+
def __init__(self, external_host: str, request_id: str, fts_message: dict[str, Any]):
|
|
639
703
|
super().__init__(external_host=external_host, request_id=request_id)
|
|
640
704
|
|
|
641
705
|
self.fts_message = fts_message
|
|
642
706
|
|
|
643
|
-
|
|
707
|
+
transfer_id = fts_message.get('tr_id')
|
|
708
|
+
if transfer_id is not None:
|
|
709
|
+
self._transfer_id = transfer_id.split("__")[-1]
|
|
644
710
|
|
|
645
711
|
self._file_metadata = fts_message['file_metadata']
|
|
646
|
-
self._multi_sources = str(fts_message.get('job_metadata', {}).get('multi_sources', '')).lower() ==
|
|
712
|
+
self._multi_sources = str(fts_message.get('job_metadata', {}).get('multi_sources', '')).lower() == 'true'
|
|
647
713
|
self._src_url = fts_message.get('src_url', None)
|
|
648
714
|
self._dst_url = fts_message.get('dst_url', None)
|
|
649
715
|
|
|
650
|
-
def initialize(self, session, logger=logging.log):
|
|
716
|
+
def initialize(self, session: "Session", logger: "LoggerFunction" = logging.log) -> None:
|
|
651
717
|
|
|
652
718
|
fts_message = self.fts_message
|
|
653
719
|
request_id = self.request_id
|
|
@@ -659,10 +725,11 @@ class FTS3CompletionMessageTransferStatusReport(Fts3TransferStatusReport):
|
|
|
659
725
|
new_state = RequestState.DONE
|
|
660
726
|
elif str(fts_message['t_final_transfer_state']) == FTS_COMPLETE_STATE.ERROR:
|
|
661
727
|
request = self.request(session)
|
|
662
|
-
if
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
728
|
+
if request is not None:
|
|
729
|
+
if self._is_recoverable_fts_overwrite_error(request, reason, self._file_metadata): # pylint:disable=no-member
|
|
730
|
+
new_state = RequestState.DONE
|
|
731
|
+
else:
|
|
732
|
+
new_state = RequestState.FAILED
|
|
666
733
|
|
|
667
734
|
transfer_id = self._transfer_id
|
|
668
735
|
if new_state:
|
|
@@ -705,7 +772,7 @@ class FTS3ApiTransferStatusReport(Fts3TransferStatusReport):
|
|
|
705
772
|
"""
|
|
706
773
|
Parses FTS api response
|
|
707
774
|
"""
|
|
708
|
-
def __init__(self, external_host, request_id, job_response, file_response, request=None):
|
|
775
|
+
def __init__(self, external_host: str, request_id: str, job_response: dict[str, Any], file_response: dict[str, Any], request: Optional[dict[str, Any]] = None):
|
|
709
776
|
super().__init__(external_host=external_host, request_id=request_id, request=request)
|
|
710
777
|
|
|
711
778
|
self.job_response = job_response
|
|
@@ -714,12 +781,12 @@ class FTS3ApiTransferStatusReport(Fts3TransferStatusReport):
|
|
|
714
781
|
self._transfer_id = job_response.get('job_id')
|
|
715
782
|
|
|
716
783
|
self._file_metadata = file_response['file_metadata']
|
|
717
|
-
self._multi_sources = str(job_response['job_metadata'].get('multi_sources', '')).lower() ==
|
|
784
|
+
self._multi_sources = str(job_response['job_metadata'].get('multi_sources', '')).lower() == 'true'
|
|
718
785
|
self._src_url = file_response.get('source_surl', None)
|
|
719
786
|
self._dst_url = file_response.get('dest_surl', None)
|
|
720
787
|
self.logger = logging.log
|
|
721
788
|
|
|
722
|
-
def initialize(self, session, logger=logging.log):
|
|
789
|
+
def initialize(self, session: "Session", logger=logging.log) -> None:
|
|
723
790
|
|
|
724
791
|
self.logger = logger
|
|
725
792
|
job_response = self.job_response
|
|
@@ -739,10 +806,12 @@ class FTS3ApiTransferStatusReport(Fts3TransferStatusReport):
|
|
|
739
806
|
new_state = RequestState.DONE
|
|
740
807
|
elif file_state == FTS_STATE.FAILED and job_state_is_final or \
|
|
741
808
|
file_state == FTS_STATE.FAILED and not self._multi_sources: # for multi-source transfers we must wait for the job to be in a final state
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
809
|
+
request = self.request(session)
|
|
810
|
+
if request is not None:
|
|
811
|
+
if self._is_recoverable_fts_overwrite_error(request, reason, self._file_metadata):
|
|
812
|
+
new_state = RequestState.DONE
|
|
813
|
+
else:
|
|
814
|
+
new_state = RequestState.FAILED
|
|
746
815
|
elif job_state_is_final and file_state == FTS_STATE.CANCELED:
|
|
747
816
|
new_state = RequestState.FAILED
|
|
748
817
|
elif job_state_is_final and file_state == FTS_STATE.NOT_USED:
|
|
@@ -795,16 +864,27 @@ class FTS3Transfertool(Transfertool):
|
|
|
795
864
|
"""
|
|
796
865
|
|
|
797
866
|
external_name = 'fts3'
|
|
798
|
-
required_rse_attrs = (
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
867
|
+
required_rse_attrs = (RseAttr.FTS, )
|
|
868
|
+
supported_schemes = Transfertool.supported_schemes.union(('mock', ))
|
|
869
|
+
|
|
870
|
+
def __init__(self,
|
|
871
|
+
external_host: str,
|
|
872
|
+
oidc_account: Optional[str] = None,
|
|
873
|
+
oidc_support: bool = False,
|
|
874
|
+
vo: Optional[str] = None,
|
|
875
|
+
group_bulk: int = 1,
|
|
876
|
+
group_policy: str = 'rule',
|
|
877
|
+
source_strategy: Optional[str] = None,
|
|
878
|
+
max_time_in_queue: Optional[dict[str, Any]] = None,
|
|
879
|
+
bring_online: Optional[int] = 43200,
|
|
880
|
+
default_lifetime: Optional[int] = 172800,
|
|
881
|
+
archive_timeout_override: Optional[int] = None,
|
|
882
|
+
logger: "LoggerFunction" = logging.log
|
|
883
|
+
):
|
|
803
884
|
"""
|
|
804
885
|
Initializes the transfertool
|
|
805
886
|
|
|
806
887
|
:param external_host: The external host where the transfertool API is running
|
|
807
|
-
:param oidc_account: optional oidc account to use for submission
|
|
808
888
|
"""
|
|
809
889
|
super().__init__(external_host, logger)
|
|
810
890
|
|
|
@@ -816,20 +896,23 @@ class FTS3Transfertool(Transfertool):
|
|
|
816
896
|
self.default_lifetime = default_lifetime
|
|
817
897
|
self.archive_timeout_override = archive_timeout_override
|
|
818
898
|
|
|
819
|
-
|
|
899
|
+
try:
|
|
900
|
+
tape_plugins = config_get_list("transfers", "fts3tape_metadata_plugins", raise_exception=True)
|
|
901
|
+
self.tape_metadata_plugins = [FTS3TapeMetadataPlugin(plugin.strip(" ")) for plugin in tape_plugins]
|
|
902
|
+
except (NoOptionError, NoSectionError, ValueError) as e:
|
|
903
|
+
self.logger(logging.DEBUG, f"Failed to set up any fts3 archive-metadata plugins: {e}")
|
|
904
|
+
self.tape_metadata_plugins = []
|
|
905
|
+
|
|
820
906
|
self.token = None
|
|
821
|
-
if
|
|
822
|
-
|
|
823
|
-
if
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if 'token' in token_dict:
|
|
831
|
-
self.logger(logging.DEBUG, 'Access token used as transfer token.')
|
|
832
|
-
self.token = token_dict['token']
|
|
907
|
+
if oidc_support:
|
|
908
|
+
fts_hostname = urlparse(external_host).hostname
|
|
909
|
+
if fts_hostname is not None:
|
|
910
|
+
token = request_token(audience=fts_hostname, scope='fts')
|
|
911
|
+
if token is not None:
|
|
912
|
+
self.logger(logging.INFO, 'Using a token to authenticate with FTS instance %s', fts_hostname)
|
|
913
|
+
self.token = token
|
|
914
|
+
else:
|
|
915
|
+
self.logger(logging.WARNING, 'Failed to procure a token to authenticate with FTS instance %s', fts_hostname)
|
|
833
916
|
|
|
834
917
|
self.deterministic_id = config_get_bool('conveyor', 'use_deterministic_id', False, False)
|
|
835
918
|
self.headers = {'Content-Type': 'application/json'}
|
|
@@ -849,29 +932,29 @@ class FTS3Transfertool(Transfertool):
|
|
|
849
932
|
self.scitags_exp_id, self.scitags_activity_ids = _scitags_ids(logger=logger)
|
|
850
933
|
|
|
851
934
|
@classmethod
|
|
852
|
-
def _pick_fts_servers(cls, source_rse: "RseData", dest_rse: "RseData"):
|
|
935
|
+
def _pick_fts_servers(cls, source_rse: "RseData", dest_rse: "RseData") -> Optional[list[str]]:
|
|
853
936
|
"""
|
|
854
937
|
Pick fts servers to use for submission between the two given rse
|
|
855
938
|
"""
|
|
856
|
-
source_servers = source_rse.attributes.get(
|
|
857
|
-
dest_servers = dest_rse.attributes.get(
|
|
939
|
+
source_servers = source_rse.attributes.get(RseAttr.FTS, None)
|
|
940
|
+
dest_servers = dest_rse.attributes.get(RseAttr.FTS, None)
|
|
858
941
|
if source_servers is None or dest_servers is None:
|
|
859
942
|
return None
|
|
860
943
|
|
|
861
944
|
servers_to_use = dest_servers
|
|
862
|
-
if source_rse.attributes.get(
|
|
945
|
+
if source_rse.attributes.get(RseAttr.SIGN_URL, None) == 'gcs':
|
|
863
946
|
servers_to_use = source_servers
|
|
864
947
|
|
|
865
948
|
return servers_to_use.split(',')
|
|
866
949
|
|
|
867
950
|
@classmethod
|
|
868
|
-
def can_perform_transfer(cls, source_rse: "RseData", dest_rse: "RseData"):
|
|
951
|
+
def can_perform_transfer(cls, source_rse: "RseData", dest_rse: "RseData") -> bool:
|
|
869
952
|
if cls._pick_fts_servers(source_rse, dest_rse):
|
|
870
953
|
return True
|
|
871
954
|
return False
|
|
872
955
|
|
|
873
956
|
@classmethod
|
|
874
|
-
def submission_builder_for_path(cls, transfer_path, logger=logging.log):
|
|
957
|
+
def submission_builder_for_path(cls, transfer_path: "list[DirectTransfer]", logger: "LoggerFunction" = logging.log):
|
|
875
958
|
vo = None
|
|
876
959
|
if config_get_bool('common', 'multi_vo', False, None):
|
|
877
960
|
vo = transfer_path[-1].rws.scope.vo
|
|
@@ -890,15 +973,15 @@ class FTS3Transfertool(Transfertool):
|
|
|
890
973
|
logger(logging.INFO, 'FTS3Transfertool can only submit {} hops from {}'.format(len(sub_path), [str(hop) for hop in transfer_path]))
|
|
891
974
|
|
|
892
975
|
if sub_path:
|
|
893
|
-
|
|
894
|
-
if all(
|
|
976
|
+
oidc_support = False
|
|
977
|
+
if all(_use_tokens(t) for t in sub_path):
|
|
895
978
|
logger(logging.DEBUG, 'OAuth2/OIDC available for transfer {}'.format([str(hop) for hop in sub_path]))
|
|
896
|
-
|
|
897
|
-
return sub_path, TransferToolBuilder(cls, external_host=fts_hosts[0],
|
|
979
|
+
oidc_support = True
|
|
980
|
+
return sub_path, TransferToolBuilder(cls, external_host=fts_hosts[0], oidc_support=oidc_support, vo=vo)
|
|
898
981
|
else:
|
|
899
982
|
return [], None
|
|
900
983
|
|
|
901
|
-
def group_into_submit_jobs(self, transfer_paths):
|
|
984
|
+
def group_into_submit_jobs(self, transfer_paths: "list[list[DirectTransfer]]") -> list[dict[str, Any]]:
|
|
902
985
|
jobs = bulk_group_transfers(
|
|
903
986
|
transfer_paths,
|
|
904
987
|
policy=self.group_policy,
|
|
@@ -912,11 +995,11 @@ class FTS3Transfertool(Transfertool):
|
|
|
912
995
|
)
|
|
913
996
|
return jobs
|
|
914
997
|
|
|
915
|
-
def _file_from_transfer(self, transfer, job_params):
|
|
998
|
+
def _file_from_transfer(self, transfer: "DirectTransfer", job_params: dict[str, str]) -> dict[str, Any]:
|
|
916
999
|
rws = transfer.rws
|
|
917
1000
|
checksum_to_use = _pick_fts_checksum(transfer, path_strategy=job_params['verify_checksum'])
|
|
918
1001
|
t_file = {
|
|
919
|
-
'sources': [s
|
|
1002
|
+
'sources': [transfer.source_url(s) for s in transfer.sources],
|
|
920
1003
|
'destinations': [transfer.dest_url],
|
|
921
1004
|
'metadata': {
|
|
922
1005
|
'request_id': rws.request_id,
|
|
@@ -939,13 +1022,32 @@ class FTS3Transfertool(Transfertool):
|
|
|
939
1022
|
'selection_strategy': self.source_strategy if self.source_strategy else _configured_source_strategy(transfer.rws.activity, logger=self.logger),
|
|
940
1023
|
'activity': rws.activity
|
|
941
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
if self.token:
|
|
1027
|
+
t_file['source_tokens'] = []
|
|
1028
|
+
for source in transfer.sources:
|
|
1029
|
+
src_audience = determine_audience_for_rse(rse_id=source.rse.id)
|
|
1030
|
+
src_scope = determine_scope_for_rse(rse_id=source.rse.id, scopes=['storage.read'], extra_scopes=['offline_access'])
|
|
1031
|
+
t_file['source_tokens'].append(request_token(src_audience, src_scope))
|
|
1032
|
+
|
|
1033
|
+
dst_audience = determine_audience_for_rse(transfer.dst.rse.id)
|
|
1034
|
+
# FIXME: At the time of writing, StoRM requires `storage.read` in
|
|
1035
|
+
# order to perform a stat operation.
|
|
1036
|
+
dst_scope = determine_scope_for_rse(transfer.dst.rse.id, scopes=['storage.modify', 'storage.read'], extra_scopes=['offline_access'])
|
|
1037
|
+
t_file['destination_tokens'] = [request_token(dst_audience, dst_scope)]
|
|
1038
|
+
|
|
942
1039
|
if isinstance(self.scitags_exp_id, int):
|
|
943
1040
|
activity_id = self.scitags_activity_ids.get(rws.activity)
|
|
944
1041
|
if isinstance(activity_id, int):
|
|
945
1042
|
t_file['scitag'] = self.scitags_exp_id << 6 | activity_id
|
|
1043
|
+
|
|
1044
|
+
if t_file['metadata']['dst_type'] == 'TAPE':
|
|
1045
|
+
for plugin in self.tape_metadata_plugins:
|
|
1046
|
+
t_file = deep_merge_dict(source=plugin.hints(t_file['metadata']), destination=t_file)
|
|
1047
|
+
|
|
946
1048
|
return t_file
|
|
947
1049
|
|
|
948
|
-
def submit(self, transfers, job_params, timeout=None):
|
|
1050
|
+
def submit(self, transfers: "Sequence[DirectTransfer]", job_params: dict[str, str], timeout: Optional[int] = None) -> str:
|
|
949
1051
|
"""
|
|
950
1052
|
Submit transfers to FTS3 via JSON.
|
|
951
1053
|
|
|
@@ -1031,7 +1133,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1031
1133
|
METRICS.timer('submit_transfers_fts3').observe(stopwatch.elapsed / (len(transfers) or 1))
|
|
1032
1134
|
return transfer_id
|
|
1033
1135
|
|
|
1034
|
-
def cancel(self, transfer_ids, timeout=None):
|
|
1136
|
+
def cancel(self, transfer_ids: "Sequence[str]", timeout: Optional[int] = None) -> dict[str, Any]:
|
|
1035
1137
|
"""
|
|
1036
1138
|
Cancel transfers that have been submitted to FTS3.
|
|
1037
1139
|
|
|
@@ -1059,7 +1161,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1059
1161
|
CANCEL_COUNTER.labels(state='failure', host=self.__extract_host(self.external_host)).inc()
|
|
1060
1162
|
raise Exception('Could not cancel transfer: %s', job.content)
|
|
1061
1163
|
|
|
1062
|
-
def update_priority(self, transfer_id, priority, timeout=None):
|
|
1164
|
+
def update_priority(self, transfer_id: str, priority: int, timeout: Optional[int] = None) -> dict[str, Any]:
|
|
1063
1165
|
"""
|
|
1064
1166
|
Update the priority of a transfer that has been submitted to FTS via JSON.
|
|
1065
1167
|
|
|
@@ -1087,7 +1189,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1087
1189
|
UPDATE_PRIORITY_COUNTER.labels(state='failure', host=self.__extract_host(self.external_host)).inc()
|
|
1088
1190
|
raise Exception('Could not update priority of transfer: %s', job.content)
|
|
1089
1191
|
|
|
1090
|
-
def query(self, transfer_ids, details=False, timeout=None):
|
|
1192
|
+
def query(self, transfer_ids: "Sequence[str]", details: bool = False, timeout: Optional[int] = None) -> Union[Optional[dict[str, Any]], list[dict[str, Any]]]:
|
|
1091
1193
|
"""
|
|
1092
1194
|
Query the status of a transfer in FTS3 via JSON.
|
|
1093
1195
|
|
|
@@ -1120,7 +1222,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1120
1222
|
|
|
1121
1223
|
# Public methods, not part of the common interface specification (FTS3 specific)
|
|
1122
1224
|
|
|
1123
|
-
def whoami(self):
|
|
1225
|
+
def whoami(self) -> dict[str, Any]:
|
|
1124
1226
|
"""
|
|
1125
1227
|
Returns credential information from the FTS3 server.
|
|
1126
1228
|
|
|
@@ -1141,7 +1243,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1141
1243
|
WHOAMI_COUNTER.labels(state='failure', host=self.__extract_host(self.external_host)).inc()
|
|
1142
1244
|
raise Exception('Could not retrieve credentials: %s', get_result.content)
|
|
1143
1245
|
|
|
1144
|
-
def version(self):
|
|
1246
|
+
def version(self) -> dict[str, Any]:
|
|
1145
1247
|
"""
|
|
1146
1248
|
Returns FTS3 server information.
|
|
1147
1249
|
|
|
@@ -1162,7 +1264,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1162
1264
|
VERSION_COUNTER.labels(state='failure', host=self.__extract_host(self.external_host)).inc()
|
|
1163
1265
|
raise Exception('Could not retrieve version: %s', get_result.content)
|
|
1164
1266
|
|
|
1165
|
-
def bulk_query(self, requests_by_eid, timeout=None):
|
|
1267
|
+
def bulk_query(self, requests_by_eid: dict[str, dict[str, dict[str, Any]]], timeout: Optional[int] = None) -> dict[str, Any]:
|
|
1166
1268
|
"""
|
|
1167
1269
|
Query the status of a bulk of transfers in FTS3 via JSON.
|
|
1168
1270
|
|
|
@@ -1201,11 +1303,11 @@ class FTS3Transfertool(Transfertool):
|
|
|
1201
1303
|
|
|
1202
1304
|
return responses
|
|
1203
1305
|
|
|
1204
|
-
def list_se_status(self):
|
|
1306
|
+
def list_se_status(self) -> dict[str, Any]:
|
|
1205
1307
|
"""
|
|
1206
1308
|
Get the list of banned Storage Elements.
|
|
1207
1309
|
|
|
1208
|
-
:returns: Detailed
|
|
1310
|
+
:returns: Detailed dictionary of banned Storage Elements.
|
|
1209
1311
|
"""
|
|
1210
1312
|
|
|
1211
1313
|
try:
|
|
@@ -1220,7 +1322,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1220
1322
|
return result.json()
|
|
1221
1323
|
raise Exception('Could not retrieve transfer information: %s', result.content)
|
|
1222
1324
|
|
|
1223
|
-
def get_se_config(self, storage_element):
|
|
1325
|
+
def get_se_config(self, storage_element: str) -> dict[str, Any]:
|
|
1224
1326
|
"""
|
|
1225
1327
|
Get the Json response for the configuration of a storage element.
|
|
1226
1328
|
:returns: a Json result for the configuration of a storage element.
|
|
@@ -1241,7 +1343,15 @@ class FTS3Transfertool(Transfertool):
|
|
|
1241
1343
|
return config_se
|
|
1242
1344
|
raise Exception('Could not get the configuration of %s , status code returned : %s', (storage_element, result.status_code if result else None))
|
|
1243
1345
|
|
|
1244
|
-
def set_se_config(
|
|
1346
|
+
def set_se_config(
|
|
1347
|
+
self,
|
|
1348
|
+
storage_element: str,
|
|
1349
|
+
inbound_max_active: Optional[int] = None,
|
|
1350
|
+
outbound_max_active: Optional[int] = None,
|
|
1351
|
+
inbound_max_throughput: Optional[float] = None,
|
|
1352
|
+
outbound_max_throughput: Optional[float] = None,
|
|
1353
|
+
staging: Optional[int] = None
|
|
1354
|
+
) -> dict[str, Any]:
|
|
1245
1355
|
"""
|
|
1246
1356
|
Set the configuration for a storage element. Used for alleviating transfer failures due to timeout.
|
|
1247
1357
|
|
|
@@ -1301,7 +1411,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1301
1411
|
return configSe
|
|
1302
1412
|
raise Exception('Could not set the configuration of %s , status code returned : %s', (storage_element, result.status_code if result else None))
|
|
1303
1413
|
|
|
1304
|
-
def set_se_status(self, storage_element, message, ban=True, timeout=None):
|
|
1414
|
+
def set_se_status(self, storage_element: str, message: str, ban: bool = True, timeout: Optional[int] = None) -> int:
|
|
1305
1415
|
"""
|
|
1306
1416
|
Ban a Storage Element. Used when a site is in downtime.
|
|
1307
1417
|
One can use a timeout in seconds. In that case the jobs will wait before being cancel.
|
|
@@ -1315,7 +1425,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1315
1425
|
:returns: 0 in case of success, otherwise raise Exception
|
|
1316
1426
|
"""
|
|
1317
1427
|
|
|
1318
|
-
params_dict = {'storage': storage_element, 'message': message}
|
|
1428
|
+
params_dict: dict[str, Any] = {'storage': storage_element, 'message': message}
|
|
1319
1429
|
status = 'CANCEL'
|
|
1320
1430
|
if timeout:
|
|
1321
1431
|
params_dict['timeout'] = timeout
|
|
@@ -1355,11 +1465,13 @@ class FTS3Transfertool(Transfertool):
|
|
|
1355
1465
|
# Private methods unique to the FTS3 Transfertool
|
|
1356
1466
|
|
|
1357
1467
|
@staticmethod
|
|
1358
|
-
def __extract_host(external_host):
|
|
1468
|
+
def __extract_host(external_host: str) -> Optional[str]:
|
|
1359
1469
|
# graphite does not like the dots in the FQDN
|
|
1360
|
-
|
|
1470
|
+
parsed_url = urlparse(external_host)
|
|
1471
|
+
if parsed_url.hostname:
|
|
1472
|
+
return parsed_url.hostname.replace('.', '_')
|
|
1361
1473
|
|
|
1362
|
-
def __get_transfer_baseid_voname(self):
|
|
1474
|
+
def __get_transfer_baseid_voname(self) -> tuple[Optional[str], Optional[str]]:
|
|
1363
1475
|
"""
|
|
1364
1476
|
Get transfer VO name from the external host.
|
|
1365
1477
|
|
|
@@ -1402,7 +1514,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1402
1514
|
result = (None, None)
|
|
1403
1515
|
return result
|
|
1404
1516
|
|
|
1405
|
-
def __get_deterministic_id(self, sid):
|
|
1517
|
+
def __get_deterministic_id(self, sid: str) -> Optional[str]:
|
|
1406
1518
|
"""
|
|
1407
1519
|
Get deterministic FTS job id.
|
|
1408
1520
|
|
|
@@ -1417,7 +1529,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1417
1529
|
jobid = uuid.uuid5(atlas, sid)
|
|
1418
1530
|
return str(jobid)
|
|
1419
1531
|
|
|
1420
|
-
def __bulk_query_responses(self, jobs_response, requests_by_eid):
|
|
1532
|
+
def __bulk_query_responses(self, jobs_response: list[dict[str, Any]], requests_by_eid: dict[str, dict[str, dict[str, Any]]]) -> dict[str, Any]:
|
|
1421
1533
|
if not isinstance(jobs_response, list):
|
|
1422
1534
|
jobs_response = [jobs_response]
|
|
1423
1535
|
|
|
@@ -1431,7 +1543,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1431
1543
|
FTS_STATE.FINISHEDDIRTY,
|
|
1432
1544
|
FTS_STATE.CANCELED,
|
|
1433
1545
|
FTS_STATE.FINISHED]:
|
|
1434
|
-
#
|
|
1546
|
+
# multiple source replicas jobs is still running. should wait
|
|
1435
1547
|
responses[transfer_id] = {}
|
|
1436
1548
|
continue
|
|
1437
1549
|
|
|
@@ -1461,7 +1573,7 @@ class FTS3Transfertool(Transfertool):
|
|
|
1461
1573
|
job_response['http_message'] if 'http_message' in job_response else None))
|
|
1462
1574
|
return responses
|
|
1463
1575
|
|
|
1464
|
-
def __query_details(self, transfer_id):
|
|
1576
|
+
def __query_details(self, transfer_id: str) -> Optional[dict[str, Any]]:
|
|
1465
1577
|
"""
|
|
1466
1578
|
Query the detailed status of a transfer in FTS3 via JSON.
|
|
1467
1579
|
|