rucio 37.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio might be problematic. Click here for more details.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio/cli/bin_legacy/rucio.py +2825 -0
- rucio/cli/bin_legacy/rucio_admin.py +2500 -0
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +432 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +983 -0
- rucio/client/client.py +120 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +868 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1783 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +452 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +969 -0
- rucio/common/__init__.py +13 -0
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +111 -0
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +788 -0
- rucio/common/constants.py +217 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +237 -0
- rucio/common/dumper/__init__.py +342 -0
- rucio/common/dumper/consistency.py +497 -0
- rucio/common/dumper/data_models.py +362 -0
- rucio/common/dumper/path_parsing.py +75 -0
- rucio/common/exception.py +1208 -0
- rucio/common/extra.py +31 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1409 -0
- rucio/common/plugins.py +185 -0
- rucio/common/policy.py +93 -0
- rucio/common/schema/__init__.py +200 -0
- rucio/common/schema/generic.py +416 -0
- rucio/common/schema/generic_multi_vo.py +395 -0
- rucio/common/stomp_utils.py +423 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +154 -0
- rucio/common/types.py +483 -0
- rucio/common/utils.py +1688 -0
- rucio/core/__init__.py +13 -0
- rucio/core/account.py +496 -0
- rucio/core/account_counter.py +236 -0
- rucio/core/account_limit.py +425 -0
- rucio/core/authentication.py +620 -0
- rucio/core/config.py +437 -0
- rucio/core/credential.py +224 -0
- rucio/core/did.py +3004 -0
- rucio/core/did_meta_plugins/__init__.py +252 -0
- rucio/core/did_meta_plugins/did_column_meta.py +331 -0
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
- rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
- rucio/core/did_meta_plugins/filter_engine.py +672 -0
- rucio/core/did_meta_plugins/json_meta.py +240 -0
- rucio/core/did_meta_plugins/mongo_meta.py +229 -0
- rucio/core/did_meta_plugins/postgres_meta.py +352 -0
- rucio/core/dirac.py +237 -0
- rucio/core/distance.py +187 -0
- rucio/core/exporter.py +59 -0
- rucio/core/heartbeat.py +363 -0
- rucio/core/identity.py +301 -0
- rucio/core/importer.py +260 -0
- rucio/core/lifetime_exception.py +377 -0
- rucio/core/lock.py +577 -0
- rucio/core/message.py +288 -0
- rucio/core/meta_conventions.py +203 -0
- rucio/core/monitor.py +448 -0
- rucio/core/naming_convention.py +195 -0
- rucio/core/nongrid_trace.py +136 -0
- rucio/core/oidc.py +1463 -0
- rucio/core/permission/__init__.py +161 -0
- rucio/core/permission/generic.py +1124 -0
- rucio/core/permission/generic_multi_vo.py +1144 -0
- rucio/core/quarantined_replica.py +224 -0
- rucio/core/replica.py +4483 -0
- rucio/core/replica_sorter.py +362 -0
- rucio/core/request.py +3091 -0
- rucio/core/rse.py +2079 -0
- rucio/core/rse_counter.py +185 -0
- rucio/core/rse_expression_parser.py +459 -0
- rucio/core/rse_selector.py +304 -0
- rucio/core/rule.py +4484 -0
- rucio/core/rule_grouping.py +1620 -0
- rucio/core/scope.py +181 -0
- rucio/core/subscription.py +362 -0
- rucio/core/topology.py +490 -0
- rucio/core/trace.py +375 -0
- rucio/core/transfer.py +1531 -0
- rucio/core/vo.py +169 -0
- rucio/core/volatile_replica.py +151 -0
- rucio/daemons/__init__.py +13 -0
- rucio/daemons/abacus/__init__.py +13 -0
- rucio/daemons/abacus/account.py +116 -0
- rucio/daemons/abacus/collection_replica.py +124 -0
- rucio/daemons/abacus/rse.py +117 -0
- rucio/daemons/atropos/__init__.py +13 -0
- rucio/daemons/atropos/atropos.py +242 -0
- rucio/daemons/auditor/__init__.py +289 -0
- rucio/daemons/auditor/hdfs.py +97 -0
- rucio/daemons/auditor/srmdumps.py +355 -0
- rucio/daemons/automatix/__init__.py +13 -0
- rucio/daemons/automatix/automatix.py +304 -0
- rucio/daemons/badreplicas/__init__.py +13 -0
- rucio/daemons/badreplicas/minos.py +322 -0
- rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
- rucio/daemons/badreplicas/necromancer.py +196 -0
- rucio/daemons/bb8/__init__.py +13 -0
- rucio/daemons/bb8/bb8.py +353 -0
- rucio/daemons/bb8/common.py +759 -0
- rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
- rucio/daemons/bb8/t2_background_rebalance.py +153 -0
- rucio/daemons/cache/__init__.py +13 -0
- rucio/daemons/cache/consumer.py +133 -0
- rucio/daemons/common.py +405 -0
- rucio/daemons/conveyor/__init__.py +13 -0
- rucio/daemons/conveyor/common.py +562 -0
- rucio/daemons/conveyor/finisher.py +529 -0
- rucio/daemons/conveyor/poller.py +394 -0
- rucio/daemons/conveyor/preparer.py +205 -0
- rucio/daemons/conveyor/receiver.py +179 -0
- rucio/daemons/conveyor/stager.py +133 -0
- rucio/daemons/conveyor/submitter.py +403 -0
- rucio/daemons/conveyor/throttler.py +532 -0
- rucio/daemons/follower/__init__.py +13 -0
- rucio/daemons/follower/follower.py +101 -0
- rucio/daemons/hermes/__init__.py +13 -0
- rucio/daemons/hermes/hermes.py +534 -0
- rucio/daemons/judge/__init__.py +13 -0
- rucio/daemons/judge/cleaner.py +159 -0
- rucio/daemons/judge/evaluator.py +185 -0
- rucio/daemons/judge/injector.py +162 -0
- rucio/daemons/judge/repairer.py +154 -0
- rucio/daemons/oauthmanager/__init__.py +13 -0
- rucio/daemons/oauthmanager/oauthmanager.py +198 -0
- rucio/daemons/reaper/__init__.py +13 -0
- rucio/daemons/reaper/dark_reaper.py +282 -0
- rucio/daemons/reaper/reaper.py +739 -0
- rucio/daemons/replicarecoverer/__init__.py +13 -0
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
- rucio/daemons/rsedecommissioner/__init__.py +13 -0
- rucio/daemons/rsedecommissioner/config.py +81 -0
- rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
- rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
- rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
- rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
- rucio/daemons/storage/__init__.py +13 -0
- rucio/daemons/storage/consistency/__init__.py +13 -0
- rucio/daemons/storage/consistency/actions.py +848 -0
- rucio/daemons/tracer/__init__.py +13 -0
- rucio/daemons/tracer/kronos.py +511 -0
- rucio/daemons/transmogrifier/__init__.py +13 -0
- rucio/daemons/transmogrifier/transmogrifier.py +762 -0
- rucio/daemons/undertaker/__init__.py +13 -0
- rucio/daemons/undertaker/undertaker.py +137 -0
- rucio/db/__init__.py +13 -0
- rucio/db/sqla/__init__.py +52 -0
- rucio/db/sqla/constants.py +206 -0
- rucio/db/sqla/migrate_repo/__init__.py +13 -0
- rucio/db/sqla/migrate_repo/env.py +110 -0
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
- rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
- rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
- rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
- rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
- rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
- rucio/db/sqla/models.py +1743 -0
- rucio/db/sqla/sautils.py +55 -0
- rucio/db/sqla/session.py +529 -0
- rucio/db/sqla/types.py +206 -0
- rucio/db/sqla/util.py +543 -0
- rucio/gateway/__init__.py +13 -0
- rucio/gateway/account.py +345 -0
- rucio/gateway/account_limit.py +363 -0
- rucio/gateway/authentication.py +381 -0
- rucio/gateway/config.py +227 -0
- rucio/gateway/credential.py +70 -0
- rucio/gateway/did.py +987 -0
- rucio/gateway/dirac.py +83 -0
- rucio/gateway/exporter.py +60 -0
- rucio/gateway/heartbeat.py +76 -0
- rucio/gateway/identity.py +189 -0
- rucio/gateway/importer.py +46 -0
- rucio/gateway/lifetime_exception.py +121 -0
- rucio/gateway/lock.py +153 -0
- rucio/gateway/meta_conventions.py +98 -0
- rucio/gateway/permission.py +74 -0
- rucio/gateway/quarantined_replica.py +79 -0
- rucio/gateway/replica.py +538 -0
- rucio/gateway/request.py +330 -0
- rucio/gateway/rse.py +632 -0
- rucio/gateway/rule.py +437 -0
- rucio/gateway/scope.py +100 -0
- rucio/gateway/subscription.py +280 -0
- rucio/gateway/vo.py +126 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +194 -0
- rucio/rse/protocols/cache.py +111 -0
- rucio/rse/protocols/dummy.py +100 -0
- rucio/rse/protocols/gfal.py +708 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +361 -0
- rucio/rse/protocols/rclone.py +365 -0
- rucio/rse/protocols/rfio.py +145 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +414 -0
- rucio/rse/protocols/storm.py +195 -0
- rucio/rse/protocols/webdav.py +594 -0
- rucio/rse/protocols/xrootd.py +302 -0
- rucio/rse/rsemanager.py +881 -0
- rucio/rse/translation.py +260 -0
- rucio/tests/__init__.py +13 -0
- rucio/tests/common.py +280 -0
- rucio/tests/common_server.py +149 -0
- rucio/transfertool/__init__.py +13 -0
- rucio/transfertool/bittorrent.py +200 -0
- rucio/transfertool/bittorrent_driver.py +50 -0
- rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
- rucio/transfertool/fts3.py +1600 -0
- rucio/transfertool/fts3_plugins.py +152 -0
- rucio/transfertool/globus.py +201 -0
- rucio/transfertool/globus_library.py +181 -0
- rucio/transfertool/mock.py +89 -0
- rucio/transfertool/transfertool.py +221 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +45 -0
- rucio/web/__init__.py +13 -0
- rucio/web/rest/__init__.py +13 -0
- rucio/web/rest/flaskapi/__init__.py +13 -0
- rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
- rucio/web/rest/flaskapi/v1/__init__.py +13 -0
- rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
- rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
- rucio/web/rest/flaskapi/v1/archives.py +102 -0
- rucio/web/rest/flaskapi/v1/auth.py +1644 -0
- rucio/web/rest/flaskapi/v1/common.py +426 -0
- rucio/web/rest/flaskapi/v1/config.py +304 -0
- rucio/web/rest/flaskapi/v1/credentials.py +213 -0
- rucio/web/rest/flaskapi/v1/dids.py +2340 -0
- rucio/web/rest/flaskapi/v1/dirac.py +116 -0
- rucio/web/rest/flaskapi/v1/export.py +75 -0
- rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
- rucio/web/rest/flaskapi/v1/identities.py +285 -0
- rucio/web/rest/flaskapi/v1/import.py +132 -0
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
- rucio/web/rest/flaskapi/v1/locks.py +358 -0
- rucio/web/rest/flaskapi/v1/main.py +91 -0
- rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
- rucio/web/rest/flaskapi/v1/metrics.py +36 -0
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
- rucio/web/rest/flaskapi/v1/ping.py +88 -0
- rucio/web/rest/flaskapi/v1/redirect.py +366 -0
- rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
- rucio/web/rest/flaskapi/v1/requests.py +998 -0
- rucio/web/rest/flaskapi/v1/rses.py +2250 -0
- rucio/web/rest/flaskapi/v1/rules.py +854 -0
- rucio/web/rest/flaskapi/v1/scopes.py +159 -0
- rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
- rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
- rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
- rucio/web/rest/flaskapi/v1/traces.py +137 -0
- rucio/web/rest/flaskapi/v1/types.py +20 -0
- rucio/web/rest/flaskapi/v1/vos.py +278 -0
- rucio/web/rest/main.py +18 -0
- rucio/web/rest/metrics.py +27 -0
- rucio/web/rest/ping.py +27 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
- rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
- rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
- rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
- rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
- rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
- rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
- rucio-37.0.0rc1.data/scripts/rucio +133 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
- rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
- rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
- rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
- rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
- rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
- rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
- rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
- rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
- rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
- rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
- rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
- rucio-37.0.0rc1.dist-info/METADATA +92 -0
- rucio-37.0.0rc1.dist-info/RECORD +487 -0
- rucio-37.0.0rc1.dist-info/WHEEL +5 -0
- rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
- rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
- rucio-37.0.0rc1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Conveyor is a daemon to manage file transfers.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import datetime
|
|
20
|
+
import itertools
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import re
|
|
24
|
+
import threading
|
|
25
|
+
import time
|
|
26
|
+
from itertools import groupby
|
|
27
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
28
|
+
|
|
29
|
+
from requests.exceptions import RequestException
|
|
30
|
+
from sqlalchemy.exc import DatabaseError
|
|
31
|
+
|
|
32
|
+
import rucio.db.sqla.util
|
|
33
|
+
from rucio.common.config import config_get, config_get_bool, config_get_float
|
|
34
|
+
from rucio.common.exception import DatabaseException, TransferToolTimeout, TransferToolWrongAnswer
|
|
35
|
+
from rucio.common.logging import setup_logging
|
|
36
|
+
from rucio.common.stopwatch import Stopwatch
|
|
37
|
+
from rucio.common.utils import dict_chunks
|
|
38
|
+
from rucio.core import request as request_core
|
|
39
|
+
from rucio.core import transfer as transfer_core
|
|
40
|
+
from rucio.core.monitor import MetricManager
|
|
41
|
+
from rucio.core.topology import ExpiringObjectCache, Topology
|
|
42
|
+
from rucio.daemons.common import ProducerConsumerDaemon, db_workqueue
|
|
43
|
+
from rucio.db.sqla.constants import MYSQL_LOCK_WAIT_TIMEOUT_EXCEEDED, ORACLE_DEADLOCK_DETECTED_REGEX, ORACLE_RESOURCE_BUSY_REGEX, RequestState, RequestType
|
|
44
|
+
from rucio.transfertool.fts3 import FTS3Transfertool
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from collections.abc import Mapping, Sequence
|
|
48
|
+
from types import FrameType
|
|
49
|
+
|
|
50
|
+
from rucio.common.types import LoggerFunction
|
|
51
|
+
from rucio.daemons.common import HeartbeatHandler
|
|
52
|
+
from rucio.transfertool.transfertool import Transfertool
|
|
53
|
+
|
|
54
|
+
GRACEFUL_STOP = threading.Event()
|
|
55
|
+
METRICS = MetricManager(module=__name__)
|
|
56
|
+
DAEMON_NAME = 'conveyor-poller'
|
|
57
|
+
|
|
58
|
+
TRANSFER_TOOL = config_get('conveyor', 'transfertool', False, None) # NOTE: This should eventually be completely removed, as it can be fetched from the request
|
|
59
|
+
FILTER_TRANSFERTOOL = config_get('conveyor', 'filter_transfertool', False, None) # NOTE: TRANSFERTOOL to filter requests on
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _fetch_requests(
|
|
63
|
+
db_bulk: int,
|
|
64
|
+
older_than: int,
|
|
65
|
+
activity_shares: Optional['Mapping[str, float]'],
|
|
66
|
+
transfertool: Optional[str],
|
|
67
|
+
filter_transfertool: Optional[str],
|
|
68
|
+
cached_topology: Optional[ExpiringObjectCache],
|
|
69
|
+
activity: str,
|
|
70
|
+
set_last_processed_by: bool,
|
|
71
|
+
heartbeat_handler: "HeartbeatHandler"
|
|
72
|
+
) -> tuple[bool, list[dict[str, Any]]]:
|
|
73
|
+
worker_number, total_workers, logger = heartbeat_handler.live()
|
|
74
|
+
|
|
75
|
+
logger(logging.DEBUG, 'Start to poll transfers older than %i seconds for activity %s using transfer tool: %s' % (older_than, activity, filter_transfertool))
|
|
76
|
+
|
|
77
|
+
topology = cached_topology.get() if cached_topology else Topology()
|
|
78
|
+
transfs = request_core.get_and_mark_next(
|
|
79
|
+
rse_collection=topology,
|
|
80
|
+
request_type=[RequestType.TRANSFER, RequestType.STAGEIN, RequestType.STAGEOUT],
|
|
81
|
+
state=[RequestState.SUBMITTED],
|
|
82
|
+
processed_by=heartbeat_handler.short_executable if set_last_processed_by else None,
|
|
83
|
+
limit=db_bulk,
|
|
84
|
+
older_than=datetime.datetime.utcnow() - datetime.timedelta(seconds=older_than) if older_than else None,
|
|
85
|
+
total_workers=total_workers,
|
|
86
|
+
worker_number=worker_number,
|
|
87
|
+
mode_all=True,
|
|
88
|
+
hash_variable='id',
|
|
89
|
+
activity=activity,
|
|
90
|
+
activity_shares=activity_shares,
|
|
91
|
+
transfertool=filter_transfertool,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if transfertool and not filter_transfertool:
|
|
95
|
+
# only keep transfers which don't have any transfertool set, or have one equal to TRANSFER_TOOL
|
|
96
|
+
transfs_tmp = [t for t in transfs if not t['transfertool'] or t['transfertool'] == transfertool]
|
|
97
|
+
if len(transfs_tmp) != len(transfs):
|
|
98
|
+
logger(logging.INFO, 'Skipping %i transfers because of mismatched transfertool', len(transfs) - len(transfs_tmp))
|
|
99
|
+
transfs = transfs_tmp
|
|
100
|
+
|
|
101
|
+
if transfs:
|
|
102
|
+
logger(logging.DEBUG, 'Polling %i transfers for activity %s' % (len(transfs), activity))
|
|
103
|
+
|
|
104
|
+
must_sleep = False
|
|
105
|
+
if len(transfs) < db_bulk / 2:
|
|
106
|
+
logger(logging.INFO, "Only %s transfers for activity %s, which is less than half of the bulk %s" % (len(transfs), activity, db_bulk))
|
|
107
|
+
must_sleep = True
|
|
108
|
+
|
|
109
|
+
return must_sleep, transfs
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _handle_requests(
|
|
113
|
+
transfs: list[dict[str, Any]],
|
|
114
|
+
fts_bulk: int,
|
|
115
|
+
multi_vo: Optional[bool],
|
|
116
|
+
timeout: Optional[int],
|
|
117
|
+
transfertool: str,
|
|
118
|
+
transfer_stats_manager: request_core.TransferStatsManager,
|
|
119
|
+
oidc_support: bool,
|
|
120
|
+
*,
|
|
121
|
+
logger: "LoggerFunction" = logging.log,
|
|
122
|
+
) -> None:
|
|
123
|
+
transfs.sort(key=lambda t: (t['external_host'] or '',
|
|
124
|
+
t['scope'].vo if multi_vo else '',
|
|
125
|
+
t['external_id'] or '',
|
|
126
|
+
t['request_id'] or ''))
|
|
127
|
+
for (external_host, vo), transfers_for_host in groupby(transfs, key=lambda t: (t['external_host'],
|
|
128
|
+
t['scope'].vo if multi_vo else None)):
|
|
129
|
+
transfers_by_eid = {}
|
|
130
|
+
for external_id, xfers in groupby(transfers_for_host, key=lambda t: t['external_id']):
|
|
131
|
+
transfers_by_eid[external_id] = {t['request_id']: t for t in xfers}
|
|
132
|
+
|
|
133
|
+
for chunk in dict_chunks(transfers_by_eid, fts_bulk):
|
|
134
|
+
try:
|
|
135
|
+
transfertool_cls = transfer_core.TRANSFERTOOL_CLASSES_BY_NAME.get(transfertool, FTS3Transfertool)
|
|
136
|
+
|
|
137
|
+
transfertool_kwargs = {}
|
|
138
|
+
if transfertool_cls.external_name == FTS3Transfertool.external_name:
|
|
139
|
+
transfertool_kwargs.update({
|
|
140
|
+
'vo': vo,
|
|
141
|
+
'oidc_support': oidc_support,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
transfertool_obj = transfertool_cls(external_host=external_host, **transfertool_kwargs)
|
|
145
|
+
poll_transfers(
|
|
146
|
+
transfertool_obj=transfertool_obj,
|
|
147
|
+
transfers_by_eid=chunk,
|
|
148
|
+
transfer_stats_manager=transfer_stats_manager,
|
|
149
|
+
timeout=timeout,
|
|
150
|
+
logger=logger,
|
|
151
|
+
)
|
|
152
|
+
except Exception:
|
|
153
|
+
logger(logging.ERROR, 'Exception', exc_info=True)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def poller(
|
|
157
|
+
once: bool = False,
|
|
158
|
+
activities: Optional['Sequence[str]'] = None,
|
|
159
|
+
sleep_time: int = 60,
|
|
160
|
+
fts_bulk: int = 100,
|
|
161
|
+
db_bulk: int = 1000,
|
|
162
|
+
older_than: int = 60,
|
|
163
|
+
activity_shares: Optional['Mapping[str, float]'] = None,
|
|
164
|
+
partition_wait_time: int = 10,
|
|
165
|
+
transfertool: Optional[str] = TRANSFER_TOOL,
|
|
166
|
+
filter_transfertool: Optional[str] = FILTER_TRANSFERTOOL,
|
|
167
|
+
cached_topology: Optional[ExpiringObjectCache] = None,
|
|
168
|
+
total_threads: int = 1,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Main loop to check the status of a transfer primitive with a transfertool.
|
|
172
|
+
"""
|
|
173
|
+
timeout = config_get_float('conveyor', 'poll_timeout', default=None, raise_exception=False)
|
|
174
|
+
multi_vo = config_get_bool('common', 'multi_vo', False, None)
|
|
175
|
+
oidc_support = config_get_bool('conveyor', 'poller_oidc_support', default=False, raise_exception=False)
|
|
176
|
+
|
|
177
|
+
executable = DAEMON_NAME
|
|
178
|
+
|
|
179
|
+
if activities:
|
|
180
|
+
activities = sorted(activities)
|
|
181
|
+
executable += '--activities ' + str(activities)
|
|
182
|
+
if activity_shares:
|
|
183
|
+
executable += '--activity_shares' + str(sorted(activity_shares))
|
|
184
|
+
if filter_transfertool:
|
|
185
|
+
executable += ' --filter-transfertool ' + filter_transfertool
|
|
186
|
+
|
|
187
|
+
transfer_stats_manager = request_core.TransferStatsManager()
|
|
188
|
+
|
|
189
|
+
@db_workqueue(
|
|
190
|
+
once=once,
|
|
191
|
+
graceful_stop=GRACEFUL_STOP,
|
|
192
|
+
executable=executable,
|
|
193
|
+
partition_wait_time=partition_wait_time,
|
|
194
|
+
sleep_time=sleep_time,
|
|
195
|
+
activities=activities,
|
|
196
|
+
)
|
|
197
|
+
def _db_producer(
|
|
198
|
+
*,
|
|
199
|
+
activity: str,
|
|
200
|
+
heartbeat_handler: "HeartbeatHandler"
|
|
201
|
+
) -> tuple[bool, list[dict[str, Any]]]:
|
|
202
|
+
return _fetch_requests(
|
|
203
|
+
db_bulk=db_bulk,
|
|
204
|
+
older_than=older_than,
|
|
205
|
+
activity_shares=activity_shares,
|
|
206
|
+
transfertool=transfertool,
|
|
207
|
+
filter_transfertool=filter_transfertool,
|
|
208
|
+
cached_topology=cached_topology,
|
|
209
|
+
activity=activity,
|
|
210
|
+
set_last_processed_by=not once,
|
|
211
|
+
heartbeat_handler=heartbeat_handler,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _consumer(transfs: list[dict[str, Any]]) -> None:
|
|
215
|
+
return _handle_requests(
|
|
216
|
+
transfs=transfs,
|
|
217
|
+
fts_bulk=fts_bulk,
|
|
218
|
+
multi_vo=multi_vo,
|
|
219
|
+
timeout=timeout, # type: ignore (unclear if timeout is meant to be int or float)
|
|
220
|
+
oidc_support=oidc_support,
|
|
221
|
+
transfertool=transfertool, # type: ignore (transfertool is not None)
|
|
222
|
+
transfer_stats_manager=transfer_stats_manager,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
with transfer_stats_manager:
|
|
226
|
+
ProducerConsumerDaemon(
|
|
227
|
+
producers=[_db_producer],
|
|
228
|
+
consumers=[_consumer for _ in range(total_threads)],
|
|
229
|
+
graceful_stop=GRACEFUL_STOP,
|
|
230
|
+
).run()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Graceful exit.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
GRACEFUL_STOP.set()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def run(
|
|
242
|
+
once: bool = False,
|
|
243
|
+
sleep_time: int = 60,
|
|
244
|
+
activities: Optional['Sequence[str]'] = None,
|
|
245
|
+
fts_bulk: int = 100,
|
|
246
|
+
db_bulk: int = 1000,
|
|
247
|
+
older_than: int = 60,
|
|
248
|
+
activity_shares: Optional[str] = None,
|
|
249
|
+
total_threads: int = 1
|
|
250
|
+
) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Starts up the conveyor threads.
|
|
253
|
+
"""
|
|
254
|
+
setup_logging(process_name=DAEMON_NAME)
|
|
255
|
+
|
|
256
|
+
if rucio.db.sqla.util.is_old_db():
|
|
257
|
+
raise DatabaseException('Database was not updated, daemon won\'t start')
|
|
258
|
+
|
|
259
|
+
parsed_activity_shares = None
|
|
260
|
+
if activity_shares:
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
parsed_activity_shares = {str(activity): float(share) for activity, share in json.loads(activity_shares).items()}
|
|
264
|
+
except Exception:
|
|
265
|
+
logging.critical('activity share is not a valid JSON dictionary')
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
if round(sum(parsed_activity_shares.values()), 2) != 1:
|
|
270
|
+
logging.critical('activity shares do not sum up to 1, got %s - aborting' % round(sum(parsed_activity_shares.values()), 2))
|
|
271
|
+
return
|
|
272
|
+
except Exception:
|
|
273
|
+
logging.critical('activity shares are not numbers? - aborting')
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
parsed_activity_shares.update((share, int(percentage * db_bulk)) for share, percentage in parsed_activity_shares.items())
|
|
277
|
+
logging.info('activity shares enabled: %s' % parsed_activity_shares)
|
|
278
|
+
|
|
279
|
+
cached_topology = ExpiringObjectCache(ttl=300, new_obj_fnc=lambda: Topology())
|
|
280
|
+
poller(
|
|
281
|
+
once=once,
|
|
282
|
+
fts_bulk=fts_bulk,
|
|
283
|
+
db_bulk=db_bulk,
|
|
284
|
+
older_than=older_than,
|
|
285
|
+
sleep_time=sleep_time,
|
|
286
|
+
activities=activities,
|
|
287
|
+
activity_shares=parsed_activity_shares,
|
|
288
|
+
cached_topology=cached_topology,
|
|
289
|
+
total_threads=total_threads,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def poll_transfers(
|
|
294
|
+
transfertool_obj: 'Transfertool',
|
|
295
|
+
transfers_by_eid: 'Mapping[str, Mapping[str, Any]]',
|
|
296
|
+
transfer_stats_manager: request_core.TransferStatsManager,
|
|
297
|
+
timeout: "Optional[int]" = None,
|
|
298
|
+
logger: "LoggerFunction" = logging.log
|
|
299
|
+
) -> None:
|
|
300
|
+
"""
|
|
301
|
+
Poll a list of transfers from an FTS server
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
poll_individual_transfers = False
|
|
305
|
+
try:
|
|
306
|
+
_poll_transfers(transfertool_obj, transfers_by_eid, transfer_stats_manager, timeout, logger)
|
|
307
|
+
except TransferToolWrongAnswer:
|
|
308
|
+
poll_individual_transfers = True
|
|
309
|
+
|
|
310
|
+
if poll_individual_transfers:
|
|
311
|
+
logger(logging.ERROR, 'Problem querying %s on %s. All jobs are being checked individually' % (list(transfers_by_eid), transfertool_obj))
|
|
312
|
+
for external_id, transfers in transfers_by_eid.items():
|
|
313
|
+
logger(logging.DEBUG, 'Checking %s on %s' % (external_id, transfertool_obj))
|
|
314
|
+
try:
|
|
315
|
+
_poll_transfers(transfertool_obj, {external_id: transfers}, transfer_stats_manager, timeout, logger)
|
|
316
|
+
except Exception as err:
|
|
317
|
+
logger(logging.ERROR, 'Problem querying %s on %s . Error returned : %s' % (external_id, transfertool_obj, str(err)))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _poll_transfers(
|
|
321
|
+
transfertool_obj: 'Transfertool',
|
|
322
|
+
transfers_by_eid: 'Mapping[str, Mapping[str, Any]]',
|
|
323
|
+
transfer_stats_manager: request_core.TransferStatsManager,
|
|
324
|
+
timeout: "Optional[int]" = None,
|
|
325
|
+
logger: "LoggerFunction" = logging.log
|
|
326
|
+
) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Helper function for poll_transfers which performs the actual polling and database update.
|
|
329
|
+
"""
|
|
330
|
+
is_bulk = len(transfers_by_eid) > 1
|
|
331
|
+
try:
|
|
332
|
+
stopwatch = Stopwatch()
|
|
333
|
+
logger(logging.INFO, 'Polling %i transfers against %s with timeout %s' % (len(transfers_by_eid), transfertool_obj, timeout))
|
|
334
|
+
resps = transfertool_obj.bulk_query(requests_by_eid=transfers_by_eid, timeout=timeout)
|
|
335
|
+
stopwatch.stop()
|
|
336
|
+
METRICS.timer('bulk_query_transfers').observe(stopwatch.elapsed / (len(transfers_by_eid) or 1))
|
|
337
|
+
logger(logging.DEBUG, 'Polled %s transfer requests status in %s seconds' % (len(transfers_by_eid), stopwatch.elapsed))
|
|
338
|
+
except TransferToolTimeout as error:
|
|
339
|
+
logger(logging.ERROR, str(error))
|
|
340
|
+
return
|
|
341
|
+
except TransferToolWrongAnswer as error:
|
|
342
|
+
logger(logging.ERROR, str(error))
|
|
343
|
+
if is_bulk:
|
|
344
|
+
raise # The calling context will retry transfers one-by-one
|
|
345
|
+
else:
|
|
346
|
+
return
|
|
347
|
+
except RequestException as error:
|
|
348
|
+
logger(logging.ERROR, "Failed to contact FTS server: %s" % (str(error)))
|
|
349
|
+
return
|
|
350
|
+
except Exception:
|
|
351
|
+
logger(logging.ERROR, "Failed to query FTS info", exc_info=True)
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
tss = time.time()
|
|
355
|
+
logger(logging.DEBUG, 'Updating %s transfer requests status' % (len(transfers_by_eid)))
|
|
356
|
+
cnt = 0
|
|
357
|
+
|
|
358
|
+
request_ids = set(itertools.chain.from_iterable(transfers_by_eid.values()))
|
|
359
|
+
for transfer_id in resps:
|
|
360
|
+
try:
|
|
361
|
+
transf_resp = resps[transfer_id]
|
|
362
|
+
# transf_resp is None: Lost.
|
|
363
|
+
# is Exception: Failed to get fts job status.
|
|
364
|
+
# is {}: No terminated jobs.
|
|
365
|
+
# is {request_id: {file_status}}: terminated jobs.
|
|
366
|
+
if transf_resp is None:
|
|
367
|
+
for request_id, request in transfers_by_eid[transfer_id].items():
|
|
368
|
+
transfer_core.mark_transfer_lost(request, logger=logger)
|
|
369
|
+
METRICS.counter('transfer_lost').inc()
|
|
370
|
+
elif isinstance(transf_resp, Exception):
|
|
371
|
+
logger(logging.WARNING, "Failed to poll FTS(%s) job (%s): %s" % (transfertool_obj, transfer_id, transf_resp))
|
|
372
|
+
METRICS.counter('query_transfer_exception').inc()
|
|
373
|
+
else:
|
|
374
|
+
for request_id in request_ids.intersection(transf_resp):
|
|
375
|
+
ret = transfer_core.update_transfer_state(
|
|
376
|
+
tt_status_report=transf_resp[request_id],
|
|
377
|
+
stats_manager=transfer_stats_manager,
|
|
378
|
+
logger=logger,
|
|
379
|
+
)
|
|
380
|
+
cnt += ret
|
|
381
|
+
if ret:
|
|
382
|
+
METRICS.counter('update_request_state.{updated}').labels(updated=True).inc(delta=ret)
|
|
383
|
+
else:
|
|
384
|
+
METRICS.counter('update_request_state.{updated}').labels(updated=False).inc()
|
|
385
|
+
|
|
386
|
+
# should touch transfers.
|
|
387
|
+
# Otherwise if one bulk transfer includes many requests and one is not terminated, the transfer will be poll again.
|
|
388
|
+
transfer_core.touch_transfer(transfertool_obj.external_host, transfer_id)
|
|
389
|
+
except (DatabaseException, DatabaseError) as error:
|
|
390
|
+
if re.match(ORACLE_RESOURCE_BUSY_REGEX, error.args[0]) or re.match(ORACLE_DEADLOCK_DETECTED_REGEX, error.args[0]) or MYSQL_LOCK_WAIT_TIMEOUT_EXCEEDED in error.args[0]:
|
|
391
|
+
logger(logging.WARNING, "Lock detected when handling request %s - skipping" % transfer_id)
|
|
392
|
+
else:
|
|
393
|
+
logger(logging.ERROR, 'Exception', exc_info=True)
|
|
394
|
+
logger(logging.DEBUG, 'Finished updating %s transfer requests status (%i requests state changed) in %s seconds' % (len(transfers_by_eid), cnt, (time.time() - tss)))
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import threading
|
|
17
|
+
from time import time
|
|
18
|
+
from typing import TYPE_CHECKING, Optional
|
|
19
|
+
|
|
20
|
+
import rucio.db.sqla.util
|
|
21
|
+
from rucio.common import exception
|
|
22
|
+
from rucio.common.config import config_get_list
|
|
23
|
+
from rucio.common.exception import RucioException
|
|
24
|
+
from rucio.common.logging import setup_logging
|
|
25
|
+
from rucio.core import transfer as transfer_core
|
|
26
|
+
from rucio.core.request import RequestWithSources, list_and_mark_transfer_requests_and_source_replicas, transition_requests_state_if_possible
|
|
27
|
+
from rucio.core.topology import ExpiringObjectCache, Topology
|
|
28
|
+
from rucio.core.transfer import ProtocolFactory, build_transfer_paths, list_transfer_admin_accounts, prepare_transfers
|
|
29
|
+
from rucio.daemons.common import ProducerConsumerDaemon, db_workqueue
|
|
30
|
+
from rucio.db.sqla.constants import RequestState, RequestType
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Mapping
|
|
34
|
+
from types import FrameType
|
|
35
|
+
|
|
36
|
+
from sqlalchemy.orm import Session
|
|
37
|
+
|
|
38
|
+
from rucio.common.types import LoggerFunction
|
|
39
|
+
from rucio.daemons.common import HeartbeatHandler
|
|
40
|
+
|
|
41
|
+
GRACEFUL_STOP = threading.Event()
|
|
42
|
+
DAEMON_NAME = 'conveyor-preparer'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Graceful exit.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
GRACEFUL_STOP.set()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run(
|
|
54
|
+
once: bool = False,
|
|
55
|
+
threads: int = 1,
|
|
56
|
+
sleep_time: int = 10,
|
|
57
|
+
bulk: int = 100,
|
|
58
|
+
ignore_availability: bool = False
|
|
59
|
+
) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Running the preparer daemon either once or by default in a loop until stop is called.
|
|
62
|
+
"""
|
|
63
|
+
setup_logging(process_name=DAEMON_NAME)
|
|
64
|
+
|
|
65
|
+
if rucio.db.sqla.util.is_old_db():
|
|
66
|
+
raise exception.DatabaseException('Database was not updated, daemon won\'t start')
|
|
67
|
+
|
|
68
|
+
cached_topology = ExpiringObjectCache(ttl=300, new_obj_fnc=lambda: Topology(ignore_availability=ignore_availability))
|
|
69
|
+
|
|
70
|
+
preparer(
|
|
71
|
+
once=once,
|
|
72
|
+
sleep_time=sleep_time,
|
|
73
|
+
bulk=bulk,
|
|
74
|
+
ignore_availability=ignore_availability,
|
|
75
|
+
cached_topology=cached_topology,
|
|
76
|
+
total_threads=threads
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def preparer(
|
|
81
|
+
once: bool,
|
|
82
|
+
sleep_time: int = 10,
|
|
83
|
+
bulk: int = 100,
|
|
84
|
+
ignore_availability: bool = False,
|
|
85
|
+
partition_wait_time: int = 10,
|
|
86
|
+
transfertools: Optional[list[str]] = None,
|
|
87
|
+
cached_topology: Optional[ExpiringObjectCache] = None,
|
|
88
|
+
total_threads: int = 1
|
|
89
|
+
) -> None:
|
|
90
|
+
# Make an initial heartbeat so that all instanced daemons have the correct worker number on the next try
|
|
91
|
+
executable = DAEMON_NAME
|
|
92
|
+
if not transfertools:
|
|
93
|
+
transfertools = config_get_list('conveyor', 'transfertool', False, None)
|
|
94
|
+
|
|
95
|
+
@db_workqueue(
|
|
96
|
+
once=once,
|
|
97
|
+
graceful_stop=GRACEFUL_STOP,
|
|
98
|
+
executable=executable,
|
|
99
|
+
partition_wait_time=partition_wait_time,
|
|
100
|
+
sleep_time=sleep_time)
|
|
101
|
+
def _db_producer(
|
|
102
|
+
*,
|
|
103
|
+
activity: str,
|
|
104
|
+
heartbeat_handler: "HeartbeatHandler"
|
|
105
|
+
) -> tuple[bool, tuple[Topology, dict[str, RequestWithSources]]]:
|
|
106
|
+
return _fetch_requests(
|
|
107
|
+
bulk=bulk,
|
|
108
|
+
ignore_availability=ignore_availability,
|
|
109
|
+
cached_topology=cached_topology,
|
|
110
|
+
heartbeat_handler=heartbeat_handler,
|
|
111
|
+
set_last_processed_by=not once,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _consumer(batch: tuple[Topology, "Mapping[str, RequestWithSources]"]) -> None:
|
|
115
|
+
return _handle_requests(
|
|
116
|
+
batch,
|
|
117
|
+
transfertools=transfertools,
|
|
118
|
+
bulk=bulk,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
ProducerConsumerDaemon(
|
|
122
|
+
producers=[_db_producer],
|
|
123
|
+
consumers=[_consumer for _ in range(total_threads)],
|
|
124
|
+
graceful_stop=GRACEFUL_STOP,
|
|
125
|
+
).run()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _fetch_requests(
|
|
129
|
+
bulk: int,
|
|
130
|
+
ignore_availability: bool,
|
|
131
|
+
cached_topology: Optional[ExpiringObjectCache],
|
|
132
|
+
heartbeat_handler: "HeartbeatHandler",
|
|
133
|
+
set_last_processed_by: bool,
|
|
134
|
+
*,
|
|
135
|
+
session: Optional["Session"] = None,
|
|
136
|
+
) -> tuple[bool, tuple[Topology, dict[str, RequestWithSources]]]:
|
|
137
|
+
worker_number, total_workers, logger = heartbeat_handler.live()
|
|
138
|
+
topology = cached_topology.get() if cached_topology else Topology(ignore_availability=ignore_availability)
|
|
139
|
+
topology.configure_multihop(logger=logger, session=session)
|
|
140
|
+
requests_with_sources = list_and_mark_transfer_requests_and_source_replicas(
|
|
141
|
+
rse_collection=topology,
|
|
142
|
+
processed_by=heartbeat_handler.short_executable if set_last_processed_by else None,
|
|
143
|
+
total_workers=total_workers,
|
|
144
|
+
worker_number=worker_number,
|
|
145
|
+
limit=bulk,
|
|
146
|
+
request_state=RequestState.PREPARING,
|
|
147
|
+
request_type=[RequestType.TRANSFER, RequestType.STAGEIN],
|
|
148
|
+
ignore_availability=ignore_availability,
|
|
149
|
+
session=session,
|
|
150
|
+
)
|
|
151
|
+
must_sleep = False
|
|
152
|
+
if len(requests_with_sources) < bulk / 2:
|
|
153
|
+
logger(logging.INFO, "Only %s transfers, which is less than half of the bulk %s", len(requests_with_sources), bulk)
|
|
154
|
+
must_sleep = True
|
|
155
|
+
return must_sleep, (topology, requests_with_sources)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _handle_requests(
|
|
159
|
+
batch: tuple[Topology, "Mapping[str, RequestWithSources]"],
|
|
160
|
+
*,
|
|
161
|
+
transfertools: Optional[list[str]] = None,
|
|
162
|
+
bulk: int = 100,
|
|
163
|
+
logger: "LoggerFunction" = logging.log,
|
|
164
|
+
) -> None:
|
|
165
|
+
topology, requests_with_sources = batch
|
|
166
|
+
|
|
167
|
+
if not transfertools:
|
|
168
|
+
transfertools = list(transfer_core.TRANSFERTOOL_CLASSES_BY_NAME)
|
|
169
|
+
|
|
170
|
+
start_time = time()
|
|
171
|
+
try:
|
|
172
|
+
admin_accounts = list_transfer_admin_accounts()
|
|
173
|
+
|
|
174
|
+
ret = build_transfer_paths(
|
|
175
|
+
topology=topology,
|
|
176
|
+
protocol_factory=ProtocolFactory(),
|
|
177
|
+
requests_with_sources=list(requests_with_sources.values()),
|
|
178
|
+
admin_accounts=admin_accounts,
|
|
179
|
+
preparer_mode=True,
|
|
180
|
+
logger=logger,
|
|
181
|
+
)
|
|
182
|
+
requests_handled = sum(len(i) for i in ret)
|
|
183
|
+
if not requests_handled:
|
|
184
|
+
updated_msg = 'had nothing to do'
|
|
185
|
+
else:
|
|
186
|
+
candidate_paths, reqs_no_source, reqs_scheme_mismatch, reqs_only_tape_source, _ = ret
|
|
187
|
+
updated_reqs, reqs_no_transfertool = prepare_transfers(candidate_paths, transfertools=transfertools, logger=logger)
|
|
188
|
+
updated_msg = f'updated {len(updated_reqs)}/{bulk} requests'
|
|
189
|
+
|
|
190
|
+
if reqs_no_transfertool:
|
|
191
|
+
logger(logging.INFO, "Ignoring request because of unsupported transfertool: %s", reqs_no_transfertool)
|
|
192
|
+
reqs_no_source.update(reqs_no_transfertool)
|
|
193
|
+
if reqs_no_source:
|
|
194
|
+
logger(logging.INFO, "Marking requests as no-sources: %s", reqs_no_source)
|
|
195
|
+
transition_requests_state_if_possible(reqs_no_source, RequestState.NO_SOURCES, logger=logger)
|
|
196
|
+
if reqs_only_tape_source:
|
|
197
|
+
logger(logging.INFO, "Marking requests as only-tape-sources: %s", reqs_only_tape_source)
|
|
198
|
+
transition_requests_state_if_possible(reqs_only_tape_source, RequestState.ONLY_TAPE_SOURCES, logger=logger)
|
|
199
|
+
if reqs_scheme_mismatch:
|
|
200
|
+
logger(logging.INFO, "Marking requests as scheme-mismatch: %s", reqs_scheme_mismatch)
|
|
201
|
+
transition_requests_state_if_possible(reqs_scheme_mismatch, RequestState.MISMATCH_SCHEME, logger=logger)
|
|
202
|
+
except RucioException:
|
|
203
|
+
logger(logging.ERROR, 'errored with a RucioException, retrying later', exc_info=True)
|
|
204
|
+
updated_msg = 'errored'
|
|
205
|
+
logger(logging.INFO, '%s, taking %.3f seconds' % (updated_msg, time() - start_time))
|