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/core/transfer.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");
|
|
@@ -15,10 +14,13 @@
|
|
|
15
14
|
|
|
16
15
|
import datetime
|
|
17
16
|
import logging
|
|
17
|
+
import operator
|
|
18
18
|
import re
|
|
19
|
+
import sys
|
|
19
20
|
import time
|
|
20
21
|
import traceback
|
|
21
|
-
from
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
from typing import TYPE_CHECKING, cast
|
|
22
24
|
|
|
23
25
|
from dogpile.cache import make_region
|
|
24
26
|
from dogpile.cache.api import NoValue
|
|
@@ -26,33 +28,37 @@ from sqlalchemy import select, update
|
|
|
26
28
|
from sqlalchemy.exc import IntegrityError
|
|
27
29
|
|
|
28
30
|
from rucio.common import constants
|
|
29
|
-
from rucio.common.config import config_get
|
|
30
|
-
from rucio.common.constants import SUPPORTED_PROTOCOLS
|
|
31
|
-
from rucio.common.exception import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
from rucio.
|
|
35
|
-
from rucio.core import
|
|
31
|
+
from rucio.common.config import config_get, config_get_list
|
|
32
|
+
from rucio.common.constants import SUPPORTED_PROTOCOLS, RseAttr
|
|
33
|
+
from rucio.common.exception import InvalidRSEExpression, RequestNotFound, RSEProtocolNotSupported, RucioException, UnsupportedOperation
|
|
34
|
+
from rucio.common.utils import construct_non_deterministic_pfn
|
|
35
|
+
from rucio.core import did
|
|
36
|
+
from rucio.core import message as message_core
|
|
37
|
+
from rucio.core import request as request_core
|
|
36
38
|
from rucio.core.account import list_accounts
|
|
37
39
|
from rucio.core.monitor import MetricManager
|
|
38
|
-
from rucio.core.request import
|
|
40
|
+
from rucio.core.request import DirectTransfer, RequestSource, RequestWithSources, TransferDestination, transition_request_state
|
|
39
41
|
from rucio.core.rse import RseData
|
|
40
42
|
from rucio.core.rse_expression_parser import parse_expression
|
|
41
43
|
from rucio.db.sqla import models
|
|
42
44
|
from rucio.db.sqla.constants import DIDType, RequestState, RequestType, TransferLimitDirection
|
|
43
|
-
from rucio.db.sqla.session import read_session,
|
|
45
|
+
from rucio.db.sqla.session import read_session, stream_session, transactional_session
|
|
44
46
|
from rucio.rse import rsemanager as rsemgr
|
|
45
|
-
from rucio.transfertool.
|
|
47
|
+
from rucio.transfertool.bittorrent import BittorrentTransfertool
|
|
46
48
|
from rucio.transfertool.fts3 import FTS3Transfertool
|
|
47
49
|
from rucio.transfertool.globus import GlobusTransferTool
|
|
48
50
|
from rucio.transfertool.mock import MockTransfertool
|
|
51
|
+
from rucio.transfertool.transfertool import TransferStatusReport, Transfertool
|
|
49
52
|
|
|
50
53
|
if TYPE_CHECKING:
|
|
51
|
-
from collections.abc import Callable,
|
|
54
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
|
52
55
|
from typing import Any, Optional
|
|
56
|
+
|
|
53
57
|
from sqlalchemy.orm import Session
|
|
58
|
+
|
|
54
59
|
from rucio.common.types import InternalAccount
|
|
55
60
|
from rucio.core.topology import Topology
|
|
61
|
+
from rucio.rse.protocols.protocol import RSEProtocol
|
|
56
62
|
|
|
57
63
|
LoggerFunction = Callable[..., Any]
|
|
58
64
|
|
|
@@ -69,22 +75,14 @@ WEBDAV_TRANSFER_MODE = config_get('conveyor', 'webdav_transfer_mode', False, Non
|
|
|
69
75
|
|
|
70
76
|
DEFAULT_MULTIHOP_TOMBSTONE_DELAY = int(datetime.timedelta(hours=2).total_seconds())
|
|
71
77
|
|
|
72
|
-
TRANSFERTOOL_CLASSES_BY_NAME = {
|
|
78
|
+
TRANSFERTOOL_CLASSES_BY_NAME: "dict[str, type[Transfertool]]" = {
|
|
73
79
|
FTS3Transfertool.external_name: FTS3Transfertool,
|
|
74
80
|
GlobusTransferTool.external_name: GlobusTransferTool,
|
|
75
81
|
MockTransfertool.external_name: MockTransfertool,
|
|
82
|
+
BittorrentTransfertool.external_name: BittorrentTransfertool,
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
|
|
79
|
-
class TransferDestination:
|
|
80
|
-
def __init__(self, rse_data, scheme):
|
|
81
|
-
self.rse = rse_data
|
|
82
|
-
self.scheme = scheme
|
|
83
|
-
|
|
84
|
-
def __str__(self):
|
|
85
|
-
return "dst_rse={}".format(self.rse)
|
|
86
|
-
|
|
87
|
-
|
|
88
86
|
class ProtocolFactory:
|
|
89
87
|
"""
|
|
90
88
|
Creates and caches protocol objects. Allowing to reuse them.
|
|
@@ -92,16 +90,16 @@ class ProtocolFactory:
|
|
|
92
90
|
def __init__(self):
|
|
93
91
|
self.protocols = {}
|
|
94
92
|
|
|
95
|
-
def protocol(self,
|
|
96
|
-
protocol_key = '%s_%s_%s' % (operation,
|
|
93
|
+
def protocol(self, rse: RseData, scheme: "Optional[str]", operation: str):
|
|
94
|
+
protocol_key = '%s_%s_%s' % (operation, rse.id, scheme)
|
|
97
95
|
protocol = self.protocols.get(protocol_key)
|
|
98
96
|
if not protocol:
|
|
99
|
-
protocol = rsemgr.create_protocol(
|
|
97
|
+
protocol = rsemgr.create_protocol(rse.info, operation, scheme)
|
|
100
98
|
self.protocols[protocol_key] = protocol
|
|
101
99
|
return protocol
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
class
|
|
102
|
+
class DirectTransferImplementation(DirectTransfer):
|
|
105
103
|
"""
|
|
106
104
|
The configuration for a direct (non-multi-hop) transfer. It can be a multi-source transfer.
|
|
107
105
|
|
|
@@ -110,16 +108,15 @@ class DirectTransferDefinition:
|
|
|
110
108
|
"""
|
|
111
109
|
def __init__(self, source: RequestSource, destination: TransferDestination, rws: RequestWithSources,
|
|
112
110
|
protocol_factory: ProtocolFactory, operation_src: str, operation_dest: str):
|
|
113
|
-
|
|
111
|
+
super().__init__(sources=[source], rws=rws)
|
|
114
112
|
self.destination = destination
|
|
115
113
|
|
|
116
|
-
self.rws = rws
|
|
117
114
|
self.protocol_factory = protocol_factory
|
|
118
115
|
self.operation_src = operation_src
|
|
119
116
|
self.operation_dest = operation_dest
|
|
120
117
|
|
|
121
118
|
self._dest_url = None
|
|
122
|
-
self.
|
|
119
|
+
self._source_urls = {}
|
|
123
120
|
|
|
124
121
|
def __str__(self):
|
|
125
122
|
return '{sources}--{request_id}->{destination}'.format(
|
|
@@ -129,40 +126,36 @@ class DirectTransferDefinition:
|
|
|
129
126
|
)
|
|
130
127
|
|
|
131
128
|
@property
|
|
132
|
-
def src(self):
|
|
129
|
+
def src(self) -> RequestSource:
|
|
133
130
|
return self.sources[0]
|
|
134
131
|
|
|
135
132
|
@property
|
|
136
|
-
def dst(self):
|
|
133
|
+
def dst(self) -> TransferDestination:
|
|
137
134
|
return self.destination
|
|
138
135
|
|
|
139
136
|
@property
|
|
140
|
-
def dest_url(self):
|
|
137
|
+
def dest_url(self) -> str:
|
|
141
138
|
if not self._dest_url:
|
|
142
139
|
self._dest_url = self._generate_dest_url(self.dst, self.rws, self.protocol_factory, self.operation_dest)
|
|
143
140
|
return self._dest_url
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if not
|
|
148
|
-
self.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
src.ranking)
|
|
157
|
-
for src in self.sources
|
|
158
|
-
]
|
|
159
|
-
return self._legacy_sources
|
|
142
|
+
def source_url(self, source: RequestSource) -> str:
|
|
143
|
+
url = self._source_urls.get(source.rse)
|
|
144
|
+
if not url:
|
|
145
|
+
self._source_urls[source.rse] = url = self._generate_source_url(
|
|
146
|
+
source,
|
|
147
|
+
self.dst,
|
|
148
|
+
rws=self.rws,
|
|
149
|
+
protocol_factory=self.protocol_factory,
|
|
150
|
+
operation=self.operation_src
|
|
151
|
+
)
|
|
152
|
+
return url
|
|
160
153
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
def dest_protocol(self) -> "RSEProtocol":
|
|
155
|
+
return self.protocol_factory.protocol(self.dst.rse, self.dst.scheme, self.operation_dest)
|
|
156
|
+
|
|
157
|
+
def source_protocol(self, source: RequestSource) -> "RSEProtocol":
|
|
158
|
+
return self.protocol_factory.protocol(source.rse, source.scheme, self.operation_src)
|
|
166
159
|
|
|
167
160
|
@staticmethod
|
|
168
161
|
def __rewrite_source_url(source_url, source_sign_url, dest_sign_url, source_scheme):
|
|
@@ -215,8 +208,8 @@ class DirectTransferDefinition:
|
|
|
215
208
|
protocol = protocol_factory.protocol(src.rse, src.scheme, operation)
|
|
216
209
|
|
|
217
210
|
# Compute the source URL
|
|
218
|
-
source_sign_url = src.rse.attributes.get(
|
|
219
|
-
dest_sign_url = dst.rse.attributes.get(
|
|
211
|
+
source_sign_url = src.rse.attributes.get(RseAttr.SIGN_URL, None)
|
|
212
|
+
dest_sign_url = dst.rse.attributes.get(RseAttr.SIGN_URL, None)
|
|
220
213
|
source_url = list(protocol.lfns2pfns(lfns={'scope': rws.scope.external, 'name': rws.name, 'path': src.file_path}).values())[0]
|
|
221
214
|
source_url = cls.__rewrite_source_url(source_url, source_sign_url=source_sign_url, dest_sign_url=dest_sign_url, source_scheme=src.scheme)
|
|
222
215
|
return source_url
|
|
@@ -236,35 +229,44 @@ class DirectTransferDefinition:
|
|
|
236
229
|
# naming convention, etc.
|
|
237
230
|
dsn = get_dsn(rws.scope, rws.name, rws.attributes.get('dsn', None))
|
|
238
231
|
# DQ2 path always starts with /, but prefix might not end with /
|
|
239
|
-
naming_convention = dst.rse.attributes.get(
|
|
240
|
-
|
|
232
|
+
naming_convention = dst.rse.attributes.get(RseAttr.NAMING_CONVENTION, None)
|
|
233
|
+
if rws.scope.external is not None:
|
|
234
|
+
dest_path = construct_non_deterministic_pfn(dsn, rws.scope.external, rws.name, naming_convention)
|
|
241
235
|
if dst.rse.is_tape():
|
|
242
236
|
if rws.retry_count or rws.activity == 'Recovery':
|
|
243
237
|
dest_path = '%s_%i' % (dest_path, int(time.time()))
|
|
244
238
|
|
|
245
239
|
dest_url = list(protocol.lfns2pfns(lfns={'scope': rws.scope.external, 'name': rws.name, 'path': dest_path}).values())[0]
|
|
246
240
|
|
|
247
|
-
dest_sign_url = dst.rse.attributes.get(
|
|
241
|
+
dest_sign_url = dst.rse.attributes.get(RseAttr.SIGN_URL, None)
|
|
248
242
|
dest_url = cls.__rewrite_dest_url(dest_url, dest_sign_url=dest_sign_url)
|
|
249
243
|
return dest_url
|
|
250
244
|
|
|
251
245
|
|
|
252
|
-
class
|
|
246
|
+
class StageinTransferImplementation(DirectTransferImplementation):
|
|
253
247
|
"""
|
|
254
248
|
A definition of a transfer which triggers a stagein operation.
|
|
255
249
|
- The source and destination url are identical
|
|
256
250
|
- must be from TAPE to non-TAPE RSE
|
|
257
251
|
- can only have one source
|
|
258
252
|
"""
|
|
259
|
-
def __init__(
|
|
253
|
+
def __init__(
|
|
254
|
+
self,
|
|
255
|
+
source: RequestSource,
|
|
256
|
+
destination: TransferDestination,
|
|
257
|
+
rws: RequestWithSources,
|
|
258
|
+
protocol_factory: ProtocolFactory,
|
|
259
|
+
operation_src: str,
|
|
260
|
+
operation_dest: str
|
|
261
|
+
):
|
|
260
262
|
if not source.rse.is_tape() or destination.rse.is_tape():
|
|
261
263
|
# allow staging_required QoS RSE to be TAPE to TAPE for pin
|
|
262
|
-
if not destination.rse.attributes.get(
|
|
264
|
+
if not destination.rse.attributes.get(RseAttr.STAGING_REQUIRED, None):
|
|
263
265
|
raise RucioException("Stageing request {} must be from TAPE to DISK rse. Got {} and {}.".format(rws, source, destination))
|
|
264
266
|
super().__init__(source, destination, rws, protocol_factory, operation_src, operation_dest)
|
|
265
267
|
|
|
266
268
|
@property
|
|
267
|
-
def dest_url(self):
|
|
269
|
+
def dest_url(self) -> str:
|
|
268
270
|
if not self._dest_url:
|
|
269
271
|
self._dest_url = self.src.url if self.src.url else self._generate_source_url(self.src,
|
|
270
272
|
self.dst,
|
|
@@ -273,19 +275,12 @@ class StageinTransferDefinition(DirectTransferDefinition):
|
|
|
273
275
|
operation=self.operation_dest)
|
|
274
276
|
return self._dest_url
|
|
275
277
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
self.src.rse.id,
|
|
283
|
-
self.src.ranking
|
|
284
|
-
)]
|
|
285
|
-
return self._legacy_sources
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def transfer_path_str(transfer_path: "list[DirectTransferDefinition]") -> str:
|
|
278
|
+
def source_url(self, source: RequestSource) -> str:
|
|
279
|
+
# Source and dest url is the same for stagein requests
|
|
280
|
+
return self.dest_url
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def transfer_path_str(transfer_path: "list[DirectTransfer]") -> str:
|
|
289
284
|
"""
|
|
290
285
|
an implementation of __str__ for a transfer path, which is a list of direct transfers, so not really an object
|
|
291
286
|
"""
|
|
@@ -312,7 +307,7 @@ def transfer_path_str(transfer_path: "list[DirectTransferDefinition]") -> str:
|
|
|
312
307
|
|
|
313
308
|
@transactional_session
|
|
314
309
|
def mark_submitting(
|
|
315
|
-
transfer: "
|
|
310
|
+
transfer: "DirectTransfer",
|
|
316
311
|
external_host: str,
|
|
317
312
|
*,
|
|
318
313
|
logger: "Callable",
|
|
@@ -329,7 +324,7 @@ def mark_submitting(
|
|
|
329
324
|
transfer.rws.scope,
|
|
330
325
|
transfer.rws.name,
|
|
331
326
|
transfer.rws.previous_attempt_id,
|
|
332
|
-
transfer.
|
|
327
|
+
[transfer.source_url(s) for s in transfer.sources],
|
|
333
328
|
transfer.dest_url,
|
|
334
329
|
external_host)
|
|
335
330
|
logger(logging.DEBUG, "%s", log_str)
|
|
@@ -358,7 +353,7 @@ def mark_submitting(
|
|
|
358
353
|
|
|
359
354
|
@transactional_session
|
|
360
355
|
def ensure_db_sources(
|
|
361
|
-
transfer_path: "list[
|
|
356
|
+
transfer_path: "list[DirectTransfer]",
|
|
362
357
|
*,
|
|
363
358
|
logger: "Callable",
|
|
364
359
|
session: "Session",
|
|
@@ -370,15 +365,15 @@ def ensure_db_sources(
|
|
|
370
365
|
desired_sources = []
|
|
371
366
|
for transfer in transfer_path:
|
|
372
367
|
|
|
373
|
-
for
|
|
368
|
+
for source in transfer.sources:
|
|
374
369
|
common_source_attrs = {
|
|
375
370
|
"scope": transfer.rws.scope,
|
|
376
371
|
"name": transfer.rws.name,
|
|
377
|
-
"rse_id":
|
|
372
|
+
"rse_id": source.rse.id,
|
|
378
373
|
"dest_rse_id": transfer.dst.rse.id,
|
|
379
|
-
"ranking":
|
|
374
|
+
"ranking": source.ranking,
|
|
380
375
|
"bytes": transfer.rws.byte_count,
|
|
381
|
-
"url":
|
|
376
|
+
"url": transfer.source_url(source),
|
|
382
377
|
"is_using": True,
|
|
383
378
|
}
|
|
384
379
|
|
|
@@ -502,7 +497,13 @@ def set_transfers_state(
|
|
|
502
497
|
|
|
503
498
|
|
|
504
499
|
@transactional_session
|
|
505
|
-
def update_transfer_state(
|
|
500
|
+
def update_transfer_state(
|
|
501
|
+
tt_status_report: TransferStatusReport,
|
|
502
|
+
stats_manager: request_core.TransferStatsManager,
|
|
503
|
+
*,
|
|
504
|
+
session: "Session",
|
|
505
|
+
logger=logging.log
|
|
506
|
+
):
|
|
506
507
|
"""
|
|
507
508
|
Used by poller and consumer to update the internal state of requests,
|
|
508
509
|
after the response by the external transfertool.
|
|
@@ -510,10 +511,11 @@ def update_transfer_state(tt_status_report: TransferStatusReport, *, session: "S
|
|
|
510
511
|
:param tt_status_report: The transfertool status update, retrieved via request.query_request().
|
|
511
512
|
:param session: The database session to use.
|
|
512
513
|
:param logger: Optional decorated logger that can be passed from the calling daemons or servers.
|
|
513
|
-
:returns
|
|
514
|
+
:returns: The number of updated requests
|
|
514
515
|
"""
|
|
515
516
|
|
|
516
517
|
request_id = tt_status_report.request_id
|
|
518
|
+
nb_updated = 0
|
|
517
519
|
try:
|
|
518
520
|
fields_to_update = tt_status_report.get_db_fields_to_update(session=session, logger=logger)
|
|
519
521
|
if not fields_to_update:
|
|
@@ -522,23 +524,39 @@ def update_transfer_state(tt_status_report: TransferStatusReport, *, session: "S
|
|
|
522
524
|
else:
|
|
523
525
|
logger(logging.INFO, 'UPDATING REQUEST %s FOR %s with changes: %s' % (str(request_id), tt_status_report, fields_to_update))
|
|
524
526
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
request = request_core.get_request(request_id, session=session)
|
|
528
|
+
updated = transition_request_state(request_id, request=request, session=session, **fields_to_update)
|
|
529
|
+
|
|
530
|
+
if not updated:
|
|
531
|
+
return nb_updated
|
|
532
|
+
nb_updated += 1
|
|
527
533
|
|
|
528
534
|
if tt_status_report.state == RequestState.FAILED:
|
|
529
535
|
if request_core.is_intermediate_hop(request):
|
|
530
|
-
request_core.handle_failed_intermediate_hop(request, session=session)
|
|
531
|
-
|
|
536
|
+
nb_updated += request_core.handle_failed_intermediate_hop(request, session=session)
|
|
537
|
+
|
|
538
|
+
if tt_status_report.state:
|
|
539
|
+
stats_manager.observe(
|
|
540
|
+
src_rse_id=request['source_rse_id'],
|
|
541
|
+
dst_rse_id=request['dest_rse_id'],
|
|
542
|
+
activity=request['activity'],
|
|
543
|
+
state=tt_status_report.state,
|
|
544
|
+
file_size=request['bytes'],
|
|
545
|
+
submitted_at=request.get('submitted_at', None),
|
|
546
|
+
started_at=fields_to_update.get('started_at', None),
|
|
547
|
+
transferred_at=fields_to_update.get('transferred_at', None),
|
|
548
|
+
session=session,
|
|
549
|
+
)
|
|
532
550
|
request_core.add_monitor_message(
|
|
533
551
|
new_state=tt_status_report.state,
|
|
534
552
|
request=request,
|
|
535
553
|
additional_fields=tt_status_report.get_monitor_msg_fields(session=session, logger=logger),
|
|
536
554
|
session=session
|
|
537
555
|
)
|
|
538
|
-
return
|
|
556
|
+
return nb_updated
|
|
539
557
|
except UnsupportedOperation as error:
|
|
540
558
|
logger(logging.WARNING, "Request %s doesn't exist - Error: %s" % (request_id, str(error).replace('\n', '')))
|
|
541
|
-
return
|
|
559
|
+
return 0
|
|
542
560
|
except Exception:
|
|
543
561
|
logger(logging.CRITICAL, "Exception", exc_info=True)
|
|
544
562
|
|
|
@@ -549,7 +567,7 @@ def mark_transfer_lost(request, *, session: "Session", logger=logging.log):
|
|
|
549
567
|
reason = "The FTS job lost"
|
|
550
568
|
|
|
551
569
|
err_msg = request_core.get_transfer_error(new_state, reason)
|
|
552
|
-
|
|
570
|
+
transition_request_state(request['id'], state=new_state, external_id=request['external_id'], err_msg=err_msg, session=session, logger=logger)
|
|
553
571
|
|
|
554
572
|
request_core.add_monitor_message(new_state=new_state, request=request, additional_fields={'reason': reason}, session=session)
|
|
555
573
|
|
|
@@ -583,21 +601,20 @@ def touch_transfer(external_host, transfer_id, *, session: "Session"):
|
|
|
583
601
|
raise RucioException(error.args)
|
|
584
602
|
|
|
585
603
|
|
|
586
|
-
|
|
587
|
-
def __create_transfer_definitions(
|
|
604
|
+
def _create_transfer_definitions(
|
|
588
605
|
topology: "Topology",
|
|
589
606
|
protocol_factory: ProtocolFactory,
|
|
590
607
|
rws: RequestWithSources,
|
|
591
|
-
sources: "
|
|
608
|
+
sources: "Iterable[RequestSource]",
|
|
592
609
|
max_sources: int,
|
|
593
|
-
multi_source_sources: "
|
|
610
|
+
multi_source_sources: "Iterable[RequestSource]",
|
|
594
611
|
limit_dest_schemes: list[str],
|
|
595
612
|
operation_src: str,
|
|
596
613
|
operation_dest: str,
|
|
597
614
|
domain: str,
|
|
598
615
|
*,
|
|
599
616
|
session: "Session",
|
|
600
|
-
) -> "dict[
|
|
617
|
+
) -> "dict[RseData, list[DirectTransfer]]":
|
|
601
618
|
"""
|
|
602
619
|
Find the all paths from sources towards the destination of the given transfer request.
|
|
603
620
|
Create the transfer definitions for each point-to-point transfer (multi-source, when possible)
|
|
@@ -615,17 +632,17 @@ def __create_transfer_definitions(
|
|
|
615
632
|
hop_src_rse = hop['source_rse']
|
|
616
633
|
hop_dst_rse = hop['dest_rse']
|
|
617
634
|
src = RequestSource(
|
|
618
|
-
|
|
635
|
+
rse=hop_src_rse,
|
|
619
636
|
file_path=source.file_path if hop_src_rse == source.rse else None,
|
|
620
637
|
ranking=source.ranking if hop_src_rse == source.rse else 0,
|
|
621
638
|
distance=hop['cumulated_distance'] if hop_src_rse == source.rse else hop['hop_distance'],
|
|
622
639
|
scheme=hop['source_scheme'],
|
|
623
640
|
)
|
|
624
641
|
dst = TransferDestination(
|
|
625
|
-
|
|
642
|
+
rse=hop_dst_rse,
|
|
626
643
|
scheme=hop['dest_scheme'],
|
|
627
644
|
)
|
|
628
|
-
hop_definition =
|
|
645
|
+
hop_definition = DirectTransferImplementation(
|
|
629
646
|
source=src,
|
|
630
647
|
destination=dst,
|
|
631
648
|
operation_src=operation_src,
|
|
@@ -654,7 +671,7 @@ def __create_transfer_definitions(
|
|
|
654
671
|
'allow_tape_source': True
|
|
655
672
|
},
|
|
656
673
|
previous_attempt_id=None,
|
|
657
|
-
|
|
674
|
+
dest_rse=hop_dst_rse,
|
|
658
675
|
account=rws.account,
|
|
659
676
|
retry_count=0,
|
|
660
677
|
priority=rws.priority,
|
|
@@ -664,7 +681,7 @@ def __create_transfer_definitions(
|
|
|
664
681
|
)
|
|
665
682
|
|
|
666
683
|
transfer_path.append(hop_definition)
|
|
667
|
-
transfers_by_source[source.rse
|
|
684
|
+
transfers_by_source[source.rse] = transfer_path
|
|
668
685
|
|
|
669
686
|
# create multi-source transfers: add additional sources if possible
|
|
670
687
|
for transfer_path in transfers_by_source.values():
|
|
@@ -703,7 +720,7 @@ def __create_transfer_definitions(
|
|
|
703
720
|
|
|
704
721
|
transfer_path[0].sources.append(
|
|
705
722
|
RequestSource(
|
|
706
|
-
|
|
723
|
+
rse=source.rse,
|
|
707
724
|
file_path=source.file_path,
|
|
708
725
|
ranking=source.ranking,
|
|
709
726
|
distance=edge.cost,
|
|
@@ -714,35 +731,35 @@ def __create_transfer_definitions(
|
|
|
714
731
|
return transfers_by_source
|
|
715
732
|
|
|
716
733
|
|
|
717
|
-
def
|
|
734
|
+
def _create_stagein_definitions(
|
|
718
735
|
rws: RequestWithSources,
|
|
719
|
-
sources: "
|
|
736
|
+
sources: "Iterable[RequestSource]",
|
|
720
737
|
limit_dest_schemes: list[str],
|
|
721
738
|
operation_src: str,
|
|
722
739
|
operation_dest: str,
|
|
723
740
|
protocol_factory: ProtocolFactory,
|
|
724
|
-
) -> "dict[
|
|
741
|
+
) -> "dict[RseData, list[DirectTransfer]]":
|
|
725
742
|
"""
|
|
726
743
|
for each source, create a single-hop transfer path with a one stageing definition inside
|
|
727
744
|
"""
|
|
728
745
|
transfers_by_source = {
|
|
729
|
-
source.rse
|
|
730
|
-
|
|
746
|
+
source.rse: [
|
|
747
|
+
cast('DirectTransfer', StageinTransferImplementation(
|
|
731
748
|
source=RequestSource(
|
|
732
|
-
|
|
749
|
+
rse=source.rse,
|
|
733
750
|
file_path=source.file_path,
|
|
734
751
|
url=source.url,
|
|
735
|
-
scheme=limit_dest_schemes,
|
|
752
|
+
scheme=limit_dest_schemes, # type: ignore (list passed instead of single scheme)
|
|
736
753
|
),
|
|
737
754
|
destination=TransferDestination(
|
|
738
|
-
|
|
739
|
-
scheme=limit_dest_schemes,
|
|
755
|
+
rse=rws.dest_rse,
|
|
756
|
+
scheme=limit_dest_schemes, # type: ignore (list passed instead of single scheme)
|
|
740
757
|
),
|
|
741
758
|
operation_src=operation_src,
|
|
742
759
|
operation_dest=operation_dest,
|
|
743
760
|
rws=rws,
|
|
744
761
|
protocol_factory=protocol_factory,
|
|
745
|
-
)
|
|
762
|
+
))
|
|
746
763
|
|
|
747
764
|
]
|
|
748
765
|
for source in sources
|
|
@@ -760,24 +777,15 @@ def get_dsn(scope, name, dsn):
|
|
|
760
777
|
return 'other'
|
|
761
778
|
|
|
762
779
|
|
|
763
|
-
def __filter_multihops_with_intermediate_tape(candidate_paths: "Iterable[list[DirectTransferDefinition]]") -> "Generator[list[DirectTransferDefinition]]":
|
|
764
|
-
# Discard multihop transfers which contain a tape source as an intermediate hop
|
|
765
|
-
for path in candidate_paths:
|
|
766
|
-
if any(transfer.src.rse.is_tape_or_staging_required() for transfer in path[1:]):
|
|
767
|
-
pass
|
|
768
|
-
else:
|
|
769
|
-
yield path
|
|
770
|
-
|
|
771
|
-
|
|
772
780
|
def __compress_multihops(
|
|
773
|
-
|
|
781
|
+
paths_by_source: "Iterable[tuple[RequestSource, Sequence[DirectTransfer]]]",
|
|
774
782
|
sources: "Iterable[RequestSource]",
|
|
775
|
-
) -> "
|
|
783
|
+
) -> "Iterator[tuple[RequestSource, Sequence[DirectTransfer]]]":
|
|
776
784
|
# Compress multihop transfers which contain other sources as part of itself.
|
|
777
785
|
# For example: multihop A->B->C and B is a source, compress A->B->C into B->C
|
|
778
786
|
source_rses = {s.rse.id for s in sources}
|
|
779
787
|
seen_source_rses = set()
|
|
780
|
-
for path in
|
|
788
|
+
for source, path in paths_by_source:
|
|
781
789
|
if len(path) > 1:
|
|
782
790
|
# find the index of the first hop starting from the end which is also a source. Path[0] will always be a source.
|
|
783
791
|
last_source_idx = next((idx for idx, hop in reversed(list(enumerate(path))) if hop.src.rse.id in source_rses), (0, None))
|
|
@@ -788,27 +796,330 @@ def __compress_multihops(
|
|
|
788
796
|
src_rse_id = path[0].src.rse.id
|
|
789
797
|
if src_rse_id not in seen_source_rses:
|
|
790
798
|
seen_source_rses.add(src_rse_id)
|
|
791
|
-
yield path
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
799
|
+
yield source, path
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
class TransferPathBuilder:
|
|
803
|
+
def __init__(
|
|
804
|
+
self,
|
|
805
|
+
topology: "Topology",
|
|
806
|
+
protocol_factory: ProtocolFactory,
|
|
807
|
+
max_sources: int,
|
|
808
|
+
preparer_mode: bool = False,
|
|
809
|
+
schemes: "Optional[list[str]]" = None,
|
|
810
|
+
failover_schemes: "Optional[list[str]]" = None,
|
|
811
|
+
requested_source_only: bool = False,
|
|
812
|
+
):
|
|
813
|
+
self.failover_schemes = failover_schemes if failover_schemes is not None else []
|
|
814
|
+
self.schemes = schemes if schemes is not None else []
|
|
815
|
+
self.topology = topology
|
|
816
|
+
self.preparer_mode = preparer_mode
|
|
817
|
+
self.protocol_factory = protocol_factory
|
|
818
|
+
self.max_sources = max_sources
|
|
819
|
+
self.requested_source_only = requested_source_only
|
|
820
|
+
|
|
821
|
+
self.definition_by_request_id = {}
|
|
822
|
+
|
|
823
|
+
def build_or_return_cached(
|
|
824
|
+
self,
|
|
825
|
+
rws: RequestWithSources,
|
|
826
|
+
sources: "Iterable[RequestSource]",
|
|
827
|
+
*,
|
|
828
|
+
logger: "LoggerFunction" = logging.log,
|
|
829
|
+
session: "Session"
|
|
830
|
+
) -> "Mapping[RseData, Sequence[DirectTransfer]]":
|
|
831
|
+
"""
|
|
832
|
+
Warning: The function currently caches the result for the given request and returns it for later calls
|
|
833
|
+
with the same request id. As a result: it can return more (or less) sources than what is provided in the
|
|
834
|
+
`sources` argument. This is done for performance reasons. As of time of writing, this behavior is not problematic
|
|
835
|
+
for the callers of this method.
|
|
836
|
+
"""
|
|
837
|
+
definition = self.definition_by_request_id.get(rws.request_id)
|
|
838
|
+
if definition:
|
|
839
|
+
return definition
|
|
840
|
+
|
|
841
|
+
transfer_schemes = self.schemes
|
|
842
|
+
if rws.previous_attempt_id and self.failover_schemes:
|
|
843
|
+
transfer_schemes = self.failover_schemes
|
|
844
|
+
|
|
845
|
+
candidate_sources = sources
|
|
846
|
+
if self.requested_source_only and rws.requested_source:
|
|
847
|
+
candidate_sources = [rws.requested_source] if rws.requested_source in sources else []
|
|
848
|
+
|
|
849
|
+
if rws.request_type == RequestType.STAGEIN:
|
|
850
|
+
definition = _create_stagein_definitions(
|
|
851
|
+
rws=rws,
|
|
852
|
+
sources=sources,
|
|
853
|
+
limit_dest_schemes=transfer_schemes,
|
|
854
|
+
operation_src='read',
|
|
855
|
+
operation_dest='write',
|
|
856
|
+
protocol_factory=self.protocol_factory
|
|
857
|
+
)
|
|
858
|
+
else:
|
|
859
|
+
definition = _create_transfer_definitions(
|
|
860
|
+
topology=self.topology,
|
|
861
|
+
rws=rws,
|
|
862
|
+
sources=candidate_sources,
|
|
863
|
+
max_sources=self.max_sources,
|
|
864
|
+
multi_source_sources=[] if self.preparer_mode else sources,
|
|
865
|
+
limit_dest_schemes=transfer_schemes,
|
|
866
|
+
operation_src='third_party_copy_read',
|
|
867
|
+
operation_dest='third_party_copy_write',
|
|
868
|
+
domain='wan',
|
|
869
|
+
protocol_factory=self.protocol_factory,
|
|
870
|
+
session=session
|
|
871
|
+
)
|
|
872
|
+
self.definition_by_request_id[rws.request_id] = definition
|
|
873
|
+
return definition
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
class _SkipSource:
|
|
877
|
+
pass
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
SKIP_SOURCE = _SkipSource()
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
class RequestRankingContext:
|
|
884
|
+
"""
|
|
885
|
+
Helper class used by SourceRankingStrategy. It allows to store additional request-specific
|
|
886
|
+
context data and access it when handling a specific source of the given request.
|
|
887
|
+
"""
|
|
888
|
+
|
|
889
|
+
def __init__(self, strategy: "SourceRankingStrategy", rws: "RequestWithSources"):
|
|
890
|
+
self.strategy = strategy
|
|
891
|
+
self.rws = rws
|
|
892
|
+
|
|
893
|
+
def apply(self, source: RequestSource) -> "int | _SkipSource":
|
|
894
|
+
verdict = self.strategy.apply(self, source)
|
|
895
|
+
if verdict is None:
|
|
896
|
+
verdict = sys.maxsize
|
|
897
|
+
return verdict
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
class SourceRankingStrategy:
|
|
901
|
+
"""
|
|
902
|
+
Represents a source ranking strategy. Used to order the sources of a request and decide
|
|
903
|
+
which will be the actual source used for the transfer.
|
|
904
|
+
|
|
905
|
+
If filter_only is True, any value other than SKIP_SOURCE returned by apply() will be ignored.
|
|
906
|
+
"""
|
|
907
|
+
filter_only: bool = False
|
|
908
|
+
|
|
909
|
+
def for_request(
|
|
910
|
+
self,
|
|
911
|
+
rws: RequestWithSources,
|
|
912
|
+
sources: "Iterable[RequestSource]",
|
|
913
|
+
*,
|
|
914
|
+
logger: "LoggerFunction" = logging.log,
|
|
915
|
+
session: "Session"
|
|
916
|
+
) -> "RequestRankingContext":
|
|
917
|
+
return RequestRankingContext(self, rws)
|
|
918
|
+
|
|
919
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
920
|
+
"""
|
|
921
|
+
Normally, this function will be called indirectly, via self.for_request(...).apply(source).
|
|
922
|
+
|
|
923
|
+
It is expected to either return SKIP_SOURCE to signal that this source must be ignored;
|
|
924
|
+
or an integer which gives the cost of the given source under the current strategy
|
|
925
|
+
(smaller cost: higher priority).
|
|
926
|
+
If `None` is returned, it will be interpreted as sys.maxsize (i.e. very low priority).
|
|
927
|
+
This is done to avoid requiring an explicit integer in filter-only strategies.
|
|
928
|
+
"""
|
|
929
|
+
pass
|
|
930
|
+
|
|
931
|
+
class _ClassNameDescriptor:
|
|
932
|
+
"""
|
|
933
|
+
Automatically set the external_name of the strategy to the class name.
|
|
934
|
+
"""
|
|
935
|
+
def __get__(self, obj, objtype=None):
|
|
936
|
+
if objtype is not None:
|
|
937
|
+
return objtype.__name__
|
|
938
|
+
return type(obj).__name__
|
|
939
|
+
|
|
940
|
+
external_name = _ClassNameDescriptor()
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
class SourceFilterStrategy(SourceRankingStrategy):
|
|
944
|
+
filter_only = True
|
|
945
|
+
|
|
810
946
|
|
|
811
|
-
|
|
947
|
+
class EnforceSourceRSEExpression(SourceFilterStrategy):
|
|
948
|
+
|
|
949
|
+
class _RankingContext(RequestRankingContext):
|
|
950
|
+
def __init__(self, strategy: "SourceRankingStrategy", rws: "RequestWithSources", allowed_source_rses: "Optional[set[str]]"):
|
|
951
|
+
super().__init__(strategy, rws)
|
|
952
|
+
self.allowed_source_rses = allowed_source_rses
|
|
953
|
+
|
|
954
|
+
def for_request(
|
|
955
|
+
self,
|
|
956
|
+
rws: RequestWithSources,
|
|
957
|
+
sources: "Iterable[RequestSource]",
|
|
958
|
+
*,
|
|
959
|
+
logger: "LoggerFunction" = logging.log,
|
|
960
|
+
session: "Session"
|
|
961
|
+
) -> "RequestRankingContext":
|
|
962
|
+
# parse source expression
|
|
963
|
+
allowed_source_rses = None
|
|
964
|
+
source_replica_expression = rws.attributes.get('source_replica_expression', None)
|
|
965
|
+
if source_replica_expression:
|
|
966
|
+
try:
|
|
967
|
+
parsed_rses = parse_expression(source_replica_expression, session=session)
|
|
968
|
+
except InvalidRSEExpression as error:
|
|
969
|
+
logger(logging.ERROR, "%s: Invalid RSE exception %s: %s", rws.request_id, source_replica_expression, str(error))
|
|
970
|
+
allowed_source_rses = set()
|
|
971
|
+
else:
|
|
972
|
+
allowed_source_rses = {x['id'] for x in parsed_rses}
|
|
973
|
+
return self._RankingContext(self, rws, allowed_source_rses)
|
|
974
|
+
|
|
975
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
976
|
+
ctx = cast('EnforceSourceRSEExpression._RankingContext', ctx)
|
|
977
|
+
if ctx.allowed_source_rses is not None and source.rse.id not in ctx.allowed_source_rses:
|
|
978
|
+
return SKIP_SOURCE
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
class SkipRestrictedRSEs(SourceFilterStrategy):
|
|
982
|
+
|
|
983
|
+
def __init__(self, admin_accounts: "Optional[set[InternalAccount]]" = None):
|
|
984
|
+
super().__init__()
|
|
985
|
+
self.admin_accounts = admin_accounts if admin_accounts is not None else []
|
|
986
|
+
|
|
987
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
988
|
+
if source.rse.attributes.get(RseAttr.RESTRICTED_READ) and ctx.rws.account not in self.admin_accounts:
|
|
989
|
+
return SKIP_SOURCE
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
class SkipBlocklistedRSEs(SourceFilterStrategy):
|
|
993
|
+
|
|
994
|
+
def __init__(self, topology: "Topology"):
|
|
995
|
+
super().__init__()
|
|
996
|
+
self.topology = topology
|
|
997
|
+
|
|
998
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
999
|
+
# Ignore blocklisted RSEs
|
|
1000
|
+
if not source.rse.columns['availability_read'] and not self.topology.ignore_availability:
|
|
1001
|
+
return SKIP_SOURCE
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
class EnforceStagingBuffer(SourceFilterStrategy):
|
|
1005
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1006
|
+
# For staging requests, the staging_buffer attribute must be correctly set
|
|
1007
|
+
if ctx.rws.request_type == RequestType.STAGEIN and source.rse.attributes.get(RseAttr.STAGING_BUFFER) != ctx.rws.dest_rse.name:
|
|
1008
|
+
return SKIP_SOURCE
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
class RestrictTapeSources(SourceFilterStrategy):
|
|
1012
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1013
|
+
# Ignore tape sources if they are not desired
|
|
1014
|
+
if source.rse.is_tape_or_staging_required() and not ctx.rws.attributes.get("allow_tape_source", True):
|
|
1015
|
+
return SKIP_SOURCE
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
class HighestAdjustedRankingFirst(SourceRankingStrategy):
|
|
1019
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1020
|
+
source_ranking_penalty = 1 if source.rse.is_tape_or_staging_required() else 0
|
|
1021
|
+
return - source.ranking + source_ranking_penalty
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
class PreferDiskOverTape(SourceRankingStrategy):
|
|
1025
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1026
|
+
return int(source.rse.is_tape_or_staging_required()) # rely on the fact that False < True
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
class PathDistance(SourceRankingStrategy):
|
|
1030
|
+
|
|
1031
|
+
class _RankingContext(RequestRankingContext):
|
|
1032
|
+
def __init__(self, strategy: "SourceRankingStrategy", rws: "RequestWithSources", paths_for_rws: "Mapping[RseData, Sequence[DirectTransfer]]"):
|
|
1033
|
+
super().__init__(strategy, rws)
|
|
1034
|
+
self.paths_for_rws = paths_for_rws
|
|
1035
|
+
|
|
1036
|
+
def __init__(self, transfer_path_builder: TransferPathBuilder):
|
|
1037
|
+
super().__init__()
|
|
1038
|
+
self.transfer_path_builder = transfer_path_builder
|
|
1039
|
+
|
|
1040
|
+
def for_request(
|
|
1041
|
+
self,
|
|
1042
|
+
rws: RequestWithSources,
|
|
1043
|
+
sources: "Iterable[RequestSource]",
|
|
1044
|
+
*,
|
|
1045
|
+
logger: "LoggerFunction" = logging.log,
|
|
1046
|
+
session: "Session"
|
|
1047
|
+
) -> "RequestRankingContext":
|
|
1048
|
+
paths_for_rws = self.transfer_path_builder.build_or_return_cached(rws, sources, logger=logger, session=session)
|
|
1049
|
+
return PathDistance._RankingContext(self, rws, paths_for_rws)
|
|
1050
|
+
|
|
1051
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1052
|
+
path = cast('PathDistance._RankingContext', ctx).paths_for_rws.get(source.rse)
|
|
1053
|
+
if not path:
|
|
1054
|
+
return SKIP_SOURCE
|
|
1055
|
+
return path[0].src.distance
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
class PreferSingleHop(PathDistance):
|
|
1059
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1060
|
+
path = cast('PathDistance._RankingContext', ctx).paths_for_rws.get(source.rse)
|
|
1061
|
+
if not path:
|
|
1062
|
+
return SKIP_SOURCE
|
|
1063
|
+
return int(len(path) > 1)
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
class FailureRate(SourceRankingStrategy):
|
|
1067
|
+
"""
|
|
1068
|
+
A source ranking strategy that ranks source nodes based on their failure rates for the past hour. Failure rate is
|
|
1069
|
+
calculated by dividing files failed by files attempted.
|
|
1070
|
+
"""
|
|
1071
|
+
class _FailureRateStat:
|
|
1072
|
+
def __init__(self) -> None:
|
|
1073
|
+
self.files_done = 0
|
|
1074
|
+
self.files_failed = 0
|
|
1075
|
+
|
|
1076
|
+
def incorporate_stat(self, stat: "Mapping[str, int]") -> None:
|
|
1077
|
+
self.files_done += stat['files_done']
|
|
1078
|
+
self.files_failed += stat['files_failed']
|
|
1079
|
+
|
|
1080
|
+
def get_failure_rate(self) -> int:
|
|
1081
|
+
files_attempted = self.files_done + self.files_failed
|
|
1082
|
+
|
|
1083
|
+
# If no files have been sent yet, return failure rate as 0
|
|
1084
|
+
if files_attempted == 0:
|
|
1085
|
+
return 0
|
|
1086
|
+
|
|
1087
|
+
return int((self.files_failed / files_attempted) * 10000)
|
|
1088
|
+
|
|
1089
|
+
def __init__(self, stats_manager: "request_core.TransferStatsManager") -> None:
|
|
1090
|
+
super().__init__()
|
|
1091
|
+
self.source_stats = {}
|
|
1092
|
+
|
|
1093
|
+
for stat in stats_manager.load_totals(
|
|
1094
|
+
datetime.datetime.utcnow() - datetime.timedelta(hours=1),
|
|
1095
|
+
by_activity=False
|
|
1096
|
+
):
|
|
1097
|
+
self.source_stats.setdefault(stat['src_rse_id'], self._FailureRateStat()).incorporate_stat(stat)
|
|
1098
|
+
|
|
1099
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1100
|
+
failure_rate = cast('FailureRate', ctx.strategy).source_stats.get(source.rse.id, self._FailureRateStat()).get_failure_rate()
|
|
1101
|
+
return failure_rate
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
class SkipSchemeMissmatch(PathDistance):
|
|
1105
|
+
filter_only = True
|
|
1106
|
+
|
|
1107
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1108
|
+
path = cast('PathDistance._RankingContext', ctx).paths_for_rws.get(source.rse)
|
|
1109
|
+
# path == None means that there is no path;
|
|
1110
|
+
# path == [] means that a path exists (according to distances) but cannot be used (scheme mismatch)
|
|
1111
|
+
if path is not None and not path:
|
|
1112
|
+
return SKIP_SOURCE
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
class SkipIntermediateTape(PathDistance):
|
|
1116
|
+
filter_only = True
|
|
1117
|
+
|
|
1118
|
+
def apply(self, ctx: RequestRankingContext, source: RequestSource) -> "Optional[int | _SkipSource]":
|
|
1119
|
+
# Discard multihop transfers which contain a tape source as an intermediate hop
|
|
1120
|
+
path = cast('PathDistance._RankingContext', ctx).paths_for_rws.get(source.rse)
|
|
1121
|
+
if path and any(transfer.src.rse.is_tape_or_staging_required() for transfer in path[1:]):
|
|
1122
|
+
return SKIP_SOURCE
|
|
812
1123
|
|
|
813
1124
|
|
|
814
1125
|
@transactional_session
|
|
@@ -837,11 +1148,55 @@ def build_transfer_paths(
|
|
|
837
1148
|
|
|
838
1149
|
Each path is a list of hops. Each hop is a transfer definition.
|
|
839
1150
|
"""
|
|
840
|
-
|
|
841
|
-
|
|
1151
|
+
transfer_path_builder = TransferPathBuilder(
|
|
1152
|
+
topology=topology,
|
|
1153
|
+
schemes=schemes,
|
|
1154
|
+
failover_schemes=failover_schemes,
|
|
1155
|
+
protocol_factory=protocol_factory,
|
|
1156
|
+
max_sources=max_sources,
|
|
1157
|
+
preparer_mode=preparer_mode,
|
|
1158
|
+
requested_source_only=requested_source_only,
|
|
1159
|
+
)
|
|
842
1160
|
|
|
843
|
-
|
|
844
|
-
|
|
1161
|
+
stats_manager = request_core.TransferStatsManager()
|
|
1162
|
+
|
|
1163
|
+
available_strategies = {
|
|
1164
|
+
EnforceSourceRSEExpression.external_name: lambda: EnforceSourceRSEExpression(),
|
|
1165
|
+
SkipBlocklistedRSEs.external_name: lambda: SkipBlocklistedRSEs(topology=topology),
|
|
1166
|
+
SkipRestrictedRSEs.external_name: lambda: SkipRestrictedRSEs(admin_accounts=admin_accounts),
|
|
1167
|
+
EnforceStagingBuffer.external_name: lambda: EnforceStagingBuffer(),
|
|
1168
|
+
RestrictTapeSources.external_name: lambda: RestrictTapeSources(),
|
|
1169
|
+
SkipSchemeMissmatch.external_name: lambda: SkipSchemeMissmatch(transfer_path_builder=transfer_path_builder),
|
|
1170
|
+
SkipIntermediateTape.external_name: lambda: SkipIntermediateTape(transfer_path_builder=transfer_path_builder),
|
|
1171
|
+
HighestAdjustedRankingFirst.external_name: lambda: HighestAdjustedRankingFirst(),
|
|
1172
|
+
PreferDiskOverTape.external_name: lambda: PreferDiskOverTape(),
|
|
1173
|
+
PathDistance.external_name: lambda: PathDistance(transfer_path_builder=transfer_path_builder),
|
|
1174
|
+
PreferSingleHop.external_name: lambda: PreferSingleHop(transfer_path_builder=transfer_path_builder),
|
|
1175
|
+
FailureRate.external_name: lambda: FailureRate(stats_manager=stats_manager),
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
default_strategies = [
|
|
1179
|
+
EnforceSourceRSEExpression.external_name,
|
|
1180
|
+
SkipBlocklistedRSEs.external_name,
|
|
1181
|
+
SkipRestrictedRSEs.external_name,
|
|
1182
|
+
EnforceStagingBuffer.external_name,
|
|
1183
|
+
RestrictTapeSources.external_name,
|
|
1184
|
+
# Without the SkipSchemeMissmatch strategy, requests will never be transitioned to the
|
|
1185
|
+
# RequestState.MISMATCH_SCHEME state. It _MUST_ be placed before the other Path-based strategies.
|
|
1186
|
+
SkipSchemeMissmatch.external_name,
|
|
1187
|
+
SkipIntermediateTape.external_name,
|
|
1188
|
+
HighestAdjustedRankingFirst.external_name,
|
|
1189
|
+
PreferDiskOverTape.external_name,
|
|
1190
|
+
PathDistance.external_name,
|
|
1191
|
+
PreferSingleHop.external_name,
|
|
1192
|
+
]
|
|
1193
|
+
strategy_names = config_get_list('transfers', 'source_ranking_strategies', default=default_strategies)
|
|
1194
|
+
|
|
1195
|
+
try:
|
|
1196
|
+
strategies = list(available_strategies[name]() for name in strategy_names)
|
|
1197
|
+
except KeyError:
|
|
1198
|
+
logger(logging.ERROR, "One of the configured source_ranking_strategies doesn't exist %s", strategy_names, exc_info=True)
|
|
1199
|
+
raise
|
|
845
1200
|
|
|
846
1201
|
if admin_accounts is None:
|
|
847
1202
|
admin_accounts = set()
|
|
@@ -860,10 +1215,6 @@ def build_transfer_paths(
|
|
|
860
1215
|
for source in all_sources:
|
|
861
1216
|
source.rse.ensure_loaded(load_name=True, load_info=True, load_attributes=True, load_columns=True, session=session)
|
|
862
1217
|
|
|
863
|
-
transfer_schemes = schemes
|
|
864
|
-
if rws.previous_attempt_id and failover_schemes:
|
|
865
|
-
transfer_schemes = failover_schemes
|
|
866
|
-
|
|
867
1218
|
# Assume request doesn't have any sources. Will be removed later if sources are found.
|
|
868
1219
|
reqs_no_source.add(rws.request_id)
|
|
869
1220
|
if not all_sources:
|
|
@@ -881,7 +1232,7 @@ def build_transfer_paths(
|
|
|
881
1232
|
if not (topology.ignore_availability or rws.dest_rse.columns['availability_write']):
|
|
882
1233
|
logger(logging.WARNING, '%s: dst RSE is blocked for write. Will skip the submission of new jobs', rws.request_id)
|
|
883
1234
|
continue
|
|
884
|
-
if rws.account not in admin_accounts and rws.dest_rse.attributes.get(
|
|
1235
|
+
if rws.account not in admin_accounts and rws.dest_rse.attributes.get(RseAttr.RESTRICTED_WRITE):
|
|
885
1236
|
logger(logging.WARNING, '%s: dst RSE is restricted for write. Will skip the submission', rws.request_id)
|
|
886
1237
|
continue
|
|
887
1238
|
|
|
@@ -892,122 +1243,55 @@ def build_transfer_paths(
|
|
|
892
1243
|
reqs_no_source.remove(rws.request_id)
|
|
893
1244
|
continue
|
|
894
1245
|
|
|
895
|
-
#
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if not topology.ignore_availability:
|
|
916
|
-
filtered_sources = filter(lambda s: s.rse.columns['availability_read'], filtered_sources)
|
|
917
|
-
# For staging requests, the staging_buffer attribute must be correctly set
|
|
918
|
-
if rws.request_type == RequestType.STAGEIN:
|
|
919
|
-
filtered_sources = filter(lambda s: s.rse.attributes.get('staging_buffer') == rws.dest_rse.name, filtered_sources)
|
|
920
|
-
# Ignore tape sources if they are not desired
|
|
921
|
-
filtered_sources = list(filtered_sources)
|
|
922
|
-
had_tape_sources = len(filtered_sources) > 0
|
|
923
|
-
if not rws.attributes.get("allow_tape_source", True):
|
|
924
|
-
filtered_sources = filter(lambda s: not s.rse.is_tape_or_staging_required(), filtered_sources)
|
|
925
|
-
|
|
926
|
-
filtered_sources = list(filtered_sources)
|
|
927
|
-
filtered_rses_log = ''
|
|
928
|
-
if len(all_sources) != len(filtered_sources):
|
|
929
|
-
filtered_rses = list(set(s.rse.name for s in all_sources).difference(s.rse.name for s in filtered_sources))
|
|
930
|
-
filtered_rses_log = '; %d dropped by filter: ' % (len(all_sources) - len(filtered_sources))
|
|
931
|
-
filtered_rses_log += ','.join(filtered_rses[:num_sources_in_logs])
|
|
932
|
-
if len(filtered_rses) > num_sources_in_logs:
|
|
933
|
-
filtered_rses_log += '... and %d others' % (len(filtered_rses) - num_sources_in_logs)
|
|
934
|
-
candidate_paths = []
|
|
935
|
-
|
|
936
|
-
candidate_sources = filtered_sources
|
|
937
|
-
if requested_source_only and rws.requested_source:
|
|
938
|
-
candidate_sources = [rws.requested_source] if rws.requested_source in filtered_sources else []
|
|
939
|
-
|
|
940
|
-
if rws.request_type == RequestType.STAGEIN:
|
|
941
|
-
paths = __create_stagein_definitions(rws=rws,
|
|
942
|
-
sources=candidate_sources,
|
|
943
|
-
limit_dest_schemes=transfer_schemes,
|
|
944
|
-
operation_src='read',
|
|
945
|
-
operation_dest='write',
|
|
946
|
-
protocol_factory=protocol_factory)
|
|
947
|
-
else:
|
|
948
|
-
paths = __create_transfer_definitions(topology=topology,
|
|
949
|
-
rws=rws,
|
|
950
|
-
sources=candidate_sources,
|
|
951
|
-
max_sources=max_sources,
|
|
952
|
-
multi_source_sources=[] if preparer_mode else filtered_sources,
|
|
953
|
-
limit_dest_schemes=[],
|
|
954
|
-
operation_src='third_party_copy_read',
|
|
955
|
-
operation_dest='third_party_copy_write',
|
|
956
|
-
domain='wan',
|
|
957
|
-
protocol_factory=protocol_factory,
|
|
958
|
-
session=session)
|
|
959
|
-
|
|
960
|
-
sources_without_path = []
|
|
961
|
-
any_source_had_scheme_mismatch = False
|
|
962
|
-
for source in candidate_sources:
|
|
963
|
-
transfer_path = paths.get(source.rse.id)
|
|
964
|
-
if transfer_path is None:
|
|
965
|
-
logger(logging.WARNING, "%s: no path from %s to %s", rws.request_id, source.rse, rws.dest_rse)
|
|
966
|
-
sources_without_path.append(source.rse.name)
|
|
967
|
-
continue
|
|
968
|
-
if not transfer_path:
|
|
969
|
-
any_source_had_scheme_mismatch = True
|
|
970
|
-
logger(logging.WARNING, "%s: no matching protocol between %s and %s", rws.request_id, source.rse, rws.dest_rse)
|
|
971
|
-
sources_without_path.append(source.rse.name)
|
|
972
|
-
continue
|
|
973
|
-
|
|
974
|
-
if len(transfer_path) > 1:
|
|
975
|
-
logger(logging.DEBUG, '%s: From %s to %s requires multihop: %s', rws.request_id, source.rse, rws.dest_rse, transfer_path_str(transfer_path))
|
|
976
|
-
|
|
977
|
-
candidate_paths.append(transfer_path)
|
|
978
|
-
|
|
979
|
-
if len(candidate_sources) != len(candidate_paths):
|
|
980
|
-
logger(logging.DEBUG, '%s: Sources after path computation: %s', rws.request_id, [str(path[0].src.rse) for path in candidate_paths])
|
|
981
|
-
|
|
982
|
-
sources_without_path_log = ''
|
|
983
|
-
if sources_without_path:
|
|
984
|
-
sources_without_path_log = '; %d dropped due to missing path: ' % len(sources_without_path)
|
|
985
|
-
sources_without_path_log += ','.join(sources_without_path[:num_sources_in_logs])
|
|
986
|
-
if len(sources_without_path) > num_sources_in_logs:
|
|
987
|
-
sources_without_path_log += '... and %d others' % (len(sources_without_path) - num_sources_in_logs)
|
|
988
|
-
|
|
989
|
-
candidate_paths = __filter_multihops_with_intermediate_tape(candidate_paths)
|
|
1246
|
+
# For each strategy name, gives the sources which were rejected by it
|
|
1247
|
+
rejected_sources = defaultdict(list)
|
|
1248
|
+
# Cost of each accepted source (lists of ordered costs: one for each ranking strategy)
|
|
1249
|
+
cost_vectors = {s: [] for s in rws.sources}
|
|
1250
|
+
for strategy in strategies:
|
|
1251
|
+
sources = list(cost_vectors)
|
|
1252
|
+
if not sources:
|
|
1253
|
+
# All sources where filtered by previous strategies. It's worthless to continue.
|
|
1254
|
+
break
|
|
1255
|
+
rws_strategy = strategy.for_request(rws, sources, logger=logger, session=session)
|
|
1256
|
+
for source in sources:
|
|
1257
|
+
verdict = rws_strategy.apply(source)
|
|
1258
|
+
if verdict is SKIP_SOURCE:
|
|
1259
|
+
rejected_sources[strategy.external_name].append(source)
|
|
1260
|
+
cost_vectors.pop(source)
|
|
1261
|
+
elif not strategy.filter_only:
|
|
1262
|
+
cost_vectors[source].append(verdict)
|
|
1263
|
+
|
|
1264
|
+
transfers_by_rse = transfer_path_builder.build_or_return_cached(rws, cost_vectors, logger=logger, session=session)
|
|
1265
|
+
candidate_paths = ((s, transfers_by_rse[s.rse]) for s, _ in sorted(cost_vectors.items(), key=operator.itemgetter(1)))
|
|
990
1266
|
if not preparer_mode:
|
|
991
1267
|
candidate_paths = __compress_multihops(candidate_paths, all_sources)
|
|
992
|
-
candidate_paths = list(
|
|
1268
|
+
candidate_paths = list(candidate_paths)
|
|
993
1269
|
|
|
994
|
-
ordered_sources_log = ','.join(
|
|
995
|
-
|
|
1270
|
+
ordered_sources_log = ', '.join(
|
|
1271
|
+
f"{s.rse}:{':'.join(str(e) for e in cost_vectors[s])}"
|
|
1272
|
+
f"{'(actual source ' + str(path[0].src.rse) + ')' if s.rse != path[0].src.rse else ''}"
|
|
1273
|
+
f"{'(multihop)' if len(path) > 1 else ''}"
|
|
1274
|
+
for s, path in candidate_paths[:num_sources_in_logs]
|
|
1275
|
+
)
|
|
996
1276
|
if len(candidate_paths) > num_sources_in_logs:
|
|
997
1277
|
ordered_sources_log += '... and %d others' % (len(candidate_paths) - num_sources_in_logs)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1278
|
+
filtered_rses_log = ''
|
|
1279
|
+
for strategy_name, sources in rejected_sources.items():
|
|
1280
|
+
filtered_rses_log += f'; {len(sources)} dropped by strategy "{strategy_name}": '
|
|
1281
|
+
filtered_rses_log += ','.join(str(s.rse) for s in sources[:num_sources_in_logs])
|
|
1282
|
+
if len(sources) > num_sources_in_logs:
|
|
1283
|
+
filtered_rses_log += '... and %d others' % (len(sources) - num_sources_in_logs)
|
|
1284
|
+
logger(logging.INFO, '%s: %d ordered sources: %s%s', rws, len(candidate_paths), ordered_sources_log, filtered_rses_log)
|
|
1001
1285
|
|
|
1002
1286
|
if not candidate_paths:
|
|
1003
1287
|
# It can happen that some sources are skipped because they are TAPE, and others because
|
|
1004
1288
|
# of scheme mismatch. However, we can only have one state in the database. I picked to
|
|
1005
1289
|
# prioritize setting only_tape_source without any particular reason.
|
|
1006
|
-
if
|
|
1290
|
+
if RestrictTapeSources.external_name in rejected_sources:
|
|
1007
1291
|
logger(logging.DEBUG, '%s: Only tape sources found' % rws.request_id)
|
|
1008
1292
|
reqs_only_tape_source.add(rws.request_id)
|
|
1009
1293
|
reqs_no_source.remove(rws.request_id)
|
|
1010
|
-
elif
|
|
1294
|
+
elif SkipSchemeMissmatch.external_name in rejected_sources:
|
|
1011
1295
|
logger(logging.DEBUG, '%s: Scheme mismatch detected' % rws.request_id)
|
|
1012
1296
|
reqs_scheme_mismatch.add(rws.request_id)
|
|
1013
1297
|
reqs_no_source.remove(rws.request_id)
|
|
@@ -1015,7 +1299,7 @@ def build_transfer_paths(
|
|
|
1015
1299
|
logger(logging.DEBUG, '%s: No candidate path found' % rws.request_id)
|
|
1016
1300
|
continue
|
|
1017
1301
|
|
|
1018
|
-
candidate_paths_by_request_id[rws.request_id] = candidate_paths
|
|
1302
|
+
candidate_paths_by_request_id[rws.request_id] = [path for _, path in candidate_paths]
|
|
1019
1303
|
reqs_no_source.remove(rws.request_id)
|
|
1020
1304
|
|
|
1021
1305
|
return candidate_paths_by_request_id, reqs_no_source, reqs_scheme_mismatch, reqs_only_tape_source, reqs_unsupported_transfertool
|
|
@@ -1104,7 +1388,7 @@ def cancel_transfer(transfertool_obj, transfer_id):
|
|
|
1104
1388
|
|
|
1105
1389
|
@transactional_session
|
|
1106
1390
|
def prepare_transfers(
|
|
1107
|
-
candidate_paths_by_request_id: "dict[str, list[list[
|
|
1391
|
+
candidate_paths_by_request_id: "dict[str, list[list[DirectTransfer]]]",
|
|
1108
1392
|
logger: "LoggerFunction" = logging.log,
|
|
1109
1393
|
transfertools: "Optional[list[str]]" = None,
|
|
1110
1394
|
*,
|
|
@@ -1142,7 +1426,7 @@ def prepare_transfers(
|
|
|
1142
1426
|
logger(logging.WARNING, '%s: all available sources were filtered', rws)
|
|
1143
1427
|
continue
|
|
1144
1428
|
|
|
1145
|
-
update_dict: dict[Any, Any] = {
|
|
1429
|
+
update_dict: "dict[Any, Any]" = {
|
|
1146
1430
|
models.Request.state.name: _throttler_request_state(
|
|
1147
1431
|
activity=rws.activity,
|
|
1148
1432
|
source_rse=selected_source.rse,
|