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,532 @@
|
|
|
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 throttler is a daemon to manage rucio internal queue.
|
|
17
|
+
"""
|
|
18
|
+
import logging
|
|
19
|
+
import math
|
|
20
|
+
import threading
|
|
21
|
+
import traceback
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
from typing import TYPE_CHECKING, Optional, TypedDict, Union
|
|
24
|
+
|
|
25
|
+
from sqlalchemy import null
|
|
26
|
+
|
|
27
|
+
import rucio.db.sqla.util
|
|
28
|
+
from rucio.common import exception
|
|
29
|
+
from rucio.common.logging import setup_logging
|
|
30
|
+
from rucio.core.monitor import MetricManager
|
|
31
|
+
from rucio.core.request import get_request_stats, re_sync_all_transfer_limits, release_all_waiting_requests, release_waiting_requests_fifo, release_waiting_requests_grouped_fifo, reset_stale_waiting_requests, set_transfer_limit_stats
|
|
32
|
+
from rucio.core.rse import RseCollection, RseData
|
|
33
|
+
from rucio.core.transfer import applicable_rse_transfer_limits
|
|
34
|
+
from rucio.daemons.common import ProducerConsumerDaemon, db_workqueue
|
|
35
|
+
from rucio.db.sqla.constants import RequestState, TransferLimitDirection
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from collections.abc import Iterator
|
|
39
|
+
from types import FrameType
|
|
40
|
+
|
|
41
|
+
from rucio.common.types import InternalAccount, LoggerFunction
|
|
42
|
+
from rucio.daemons.common import HeartbeatHandler
|
|
43
|
+
|
|
44
|
+
class LimitDict(TypedDict):
|
|
45
|
+
activity: Optional[str]
|
|
46
|
+
direction: TransferLimitDirection
|
|
47
|
+
waitings: int
|
|
48
|
+
transfers: int
|
|
49
|
+
max_transfers: int
|
|
50
|
+
rse_expression: str
|
|
51
|
+
strategy: str
|
|
52
|
+
volume: int
|
|
53
|
+
deadline: int
|
|
54
|
+
|
|
55
|
+
class AccountStatDict(TypedDict):
|
|
56
|
+
waiting: int
|
|
57
|
+
active: int
|
|
58
|
+
residual_capacity: float
|
|
59
|
+
|
|
60
|
+
class StatDict(TypedDict):
|
|
61
|
+
accounts: AccountStatDict
|
|
62
|
+
residual_capacity: float
|
|
63
|
+
waiting: int
|
|
64
|
+
active: int
|
|
65
|
+
|
|
66
|
+
class LimitStatDict(TypedDict):
|
|
67
|
+
limit: LimitDict
|
|
68
|
+
stat: StatDict
|
|
69
|
+
|
|
70
|
+
ReleaseGroupsDict = dict[
|
|
71
|
+
tuple[
|
|
72
|
+
RseData, # source_rse
|
|
73
|
+
RseData, # dest_rse
|
|
74
|
+
str, # activity
|
|
75
|
+
],
|
|
76
|
+
list[LimitStatDict] # applicable_limits
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
GRACEFUL_STOP = threading.Event()
|
|
81
|
+
METRICS = MetricManager(module=__name__)
|
|
82
|
+
DAEMON_NAME = 'conveyor-throttler'
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def throttler(
|
|
86
|
+
once: bool = False,
|
|
87
|
+
sleep_time: int = 600,
|
|
88
|
+
partition_wait_time: int = 10
|
|
89
|
+
) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Main loop to check rse transfer limits.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
logging.info('Throttler starting')
|
|
95
|
+
|
|
96
|
+
@db_workqueue(
|
|
97
|
+
once=once,
|
|
98
|
+
graceful_stop=GRACEFUL_STOP,
|
|
99
|
+
executable=DAEMON_NAME,
|
|
100
|
+
partition_wait_time=partition_wait_time,
|
|
101
|
+
sleep_time=sleep_time)
|
|
102
|
+
def _db_producer(
|
|
103
|
+
*,
|
|
104
|
+
activity: str,
|
|
105
|
+
heartbeat_handler: "HeartbeatHandler"
|
|
106
|
+
) -> tuple[bool, Optional["ReleaseGroupsDict"]]:
|
|
107
|
+
worker_number, total_workers, logger = heartbeat_handler.live()
|
|
108
|
+
if worker_number != 0:
|
|
109
|
+
logger(logging.INFO, 'Throttler thread id is not 0, will sleep. Only thread 0 will work')
|
|
110
|
+
return True, None
|
|
111
|
+
|
|
112
|
+
re_sync_all_transfer_limits()
|
|
113
|
+
rse_collection = RseCollection()
|
|
114
|
+
release_groups = _get_request_stats(rse_collection, logger=logger)
|
|
115
|
+
return True, release_groups
|
|
116
|
+
|
|
117
|
+
def _consumer(release_groups: Optional["ReleaseGroupsDict"]) -> None:
|
|
118
|
+
if release_groups is None:
|
|
119
|
+
return
|
|
120
|
+
logger = logging.log
|
|
121
|
+
logger(logging.INFO, "Throttler - schedule requests")
|
|
122
|
+
try:
|
|
123
|
+
_handle_requests(release_groups, logger=logger)
|
|
124
|
+
except Exception:
|
|
125
|
+
logger(logging.CRITICAL, "Failed to schedule requests, error: %s" % (traceback.format_exc()))
|
|
126
|
+
reset_stale_waiting_requests()
|
|
127
|
+
|
|
128
|
+
ProducerConsumerDaemon(
|
|
129
|
+
producers=[_db_producer],
|
|
130
|
+
consumers=[_consumer],
|
|
131
|
+
graceful_stop=GRACEFUL_STOP,
|
|
132
|
+
).run()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def stop(signum: Optional[int] = None, frame: Optional["FrameType"] = None) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Graceful exit.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
GRACEFUL_STOP.set()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def run(
|
|
144
|
+
once: bool = False,
|
|
145
|
+
sleep_time: int = 600
|
|
146
|
+
) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Starts up the conveyor threads.
|
|
149
|
+
"""
|
|
150
|
+
setup_logging(process_name=DAEMON_NAME)
|
|
151
|
+
|
|
152
|
+
if rucio.db.sqla.util.is_old_db():
|
|
153
|
+
raise exception.DatabaseException('Database was not updated, daemon won\'t start')
|
|
154
|
+
|
|
155
|
+
throttler(once=once, sleep_time=sleep_time)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class RequestGrouper:
|
|
159
|
+
|
|
160
|
+
class RseStatistic:
|
|
161
|
+
def __init__(self):
|
|
162
|
+
self.sources_with_limits: set[RseData] = set()
|
|
163
|
+
self.destinations_with_limits: set[RseData] = set()
|
|
164
|
+
self.unavailable_sources: set[RseData] = set()
|
|
165
|
+
self.unavailable_destinations: set[RseData] = set()
|
|
166
|
+
self.has_any_per_activity_limit = False
|
|
167
|
+
self.any_source_has_per_activity_limit = False
|
|
168
|
+
self.any_destination_has_per_activity_limit = False
|
|
169
|
+
|
|
170
|
+
def __init__(self):
|
|
171
|
+
self.waiting_transfer_groups: 'ReleaseGroupsDict' = {}
|
|
172
|
+
self.rse_stats = defaultdict(self.RseStatistic)
|
|
173
|
+
|
|
174
|
+
def record_waiting_request_group(
|
|
175
|
+
self,
|
|
176
|
+
source_rse: RseData,
|
|
177
|
+
dest_rse: RseData,
|
|
178
|
+
activity: str,
|
|
179
|
+
applicable_limits: list['LimitStatDict']
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Record a group of requests in waiting state, while computing some statistics about them.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
src_info = self.rse_stats[source_rse]
|
|
186
|
+
dst_info = self.rse_stats[dest_rse]
|
|
187
|
+
|
|
188
|
+
if dest_rse and not dest_rse.columns['availability_write']:
|
|
189
|
+
src_info.unavailable_destinations.add(dest_rse)
|
|
190
|
+
if source_rse and not source_rse.columns['availability_read']:
|
|
191
|
+
dst_info.unavailable_sources.add(source_rse)
|
|
192
|
+
for limit_stat in applicable_limits:
|
|
193
|
+
limit = limit_stat['limit']
|
|
194
|
+
limit_is_per_activity = limit['activity'] is not None
|
|
195
|
+
if source_rse and limit['direction'] == TransferLimitDirection.SOURCE:
|
|
196
|
+
src_info.has_any_per_activity_limit |= limit_is_per_activity
|
|
197
|
+
dst_info.sources_with_limits.add(source_rse)
|
|
198
|
+
dst_info.any_source_has_per_activity_limit |= limit_is_per_activity
|
|
199
|
+
else:
|
|
200
|
+
dst_info.has_any_per_activity_limit |= limit_is_per_activity
|
|
201
|
+
src_info.destinations_with_limits.add(dest_rse)
|
|
202
|
+
src_info.any_destination_has_per_activity_limit |= limit_is_per_activity
|
|
203
|
+
|
|
204
|
+
self.waiting_transfer_groups[source_rse, dest_rse, activity] = applicable_limits
|
|
205
|
+
|
|
206
|
+
def merged_groups(self) -> "ReleaseGroupsDict":
|
|
207
|
+
"""
|
|
208
|
+
Merge groups which can be handled together
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
merged_groups: "ReleaseGroupsDict" = {}
|
|
212
|
+
for (source_rse, dest_rse, activity), applicable_limits in self.waiting_transfer_groups.items():
|
|
213
|
+
|
|
214
|
+
src_info = self.rse_stats[source_rse]
|
|
215
|
+
dst_info = self.rse_stats[dest_rse]
|
|
216
|
+
|
|
217
|
+
if not dst_info.sources_with_limits and not dst_info.unavailable_sources:
|
|
218
|
+
# None of the RSEs used as sources towards dest_rse has a limit configured
|
|
219
|
+
# And none of them is unavailable.
|
|
220
|
+
# It is possible to handle all waiting requests from any source in a single bulk
|
|
221
|
+
source_rse = None
|
|
222
|
+
|
|
223
|
+
if source_rse and not src_info.destinations_with_limits and not src_info.unavailable_destinations:
|
|
224
|
+
# All waiting requests from this source to any of the destination rses can be handled together
|
|
225
|
+
dest_rse = None
|
|
226
|
+
|
|
227
|
+
if source_rse and dest_rse:
|
|
228
|
+
if not dst_info.has_any_per_activity_limit and not src_info.has_any_per_activity_limit:
|
|
229
|
+
# All limits for this source/destination pair are configured per "all_activities",
|
|
230
|
+
# We can thus handle all activities in a single bulk.
|
|
231
|
+
activity = None
|
|
232
|
+
elif dest_rse:
|
|
233
|
+
if not dst_info.has_any_per_activity_limit and not dst_info.any_source_has_per_activity_limit:
|
|
234
|
+
# All limits, on all sources est_rse are configured
|
|
235
|
+
# per "all_activities". We don't need to handle each activity separately.
|
|
236
|
+
activity = None
|
|
237
|
+
elif source_rse:
|
|
238
|
+
if not src_info.has_any_per_activity_limit and not src_info.any_destination_has_per_activity_limit:
|
|
239
|
+
activity = None
|
|
240
|
+
|
|
241
|
+
merged_groups.setdefault((source_rse, dest_rse, activity), applicable_limits) # type: ignore
|
|
242
|
+
|
|
243
|
+
return merged_groups
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _get_request_stats(
|
|
247
|
+
rse_collection: RseCollection,
|
|
248
|
+
*,
|
|
249
|
+
logger: "LoggerFunction" = logging.log
|
|
250
|
+
) -> "ReleaseGroupsDict":
|
|
251
|
+
"""
|
|
252
|
+
Group waiting requests into arbitrary groups for bulk handling.
|
|
253
|
+
The current grouping (source rse + dest rse + activity) was dictated
|
|
254
|
+
by SQL queries used to release requests later in the throttler.
|
|
255
|
+
Any combination of the group attributes can be None. For example,
|
|
256
|
+
if activity and source rse are None, later code will work on all
|
|
257
|
+
requests towards their common destination rse in one go.
|
|
258
|
+
|
|
259
|
+
For each group, find the limits which apply to that group. The same
|
|
260
|
+
limit can be shared by multiple groups.
|
|
261
|
+
|
|
262
|
+
For each limit, compute the total number of active and waiting transfers
|
|
263
|
+
subject to that limit.
|
|
264
|
+
"""
|
|
265
|
+
logging.info("Throttler retrieve requests statistics")
|
|
266
|
+
|
|
267
|
+
db_stats = get_request_stats( # type: ignore (Session parameter is missing)
|
|
268
|
+
state=[RequestState.QUEUED,
|
|
269
|
+
RequestState.SUBMITTING,
|
|
270
|
+
RequestState.SUBMITTED,
|
|
271
|
+
RequestState.WAITING],
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# for each active limit, compute how many waiting and active transfers are currently in the database
|
|
275
|
+
limit_stats = {}
|
|
276
|
+
# for each group of (source_rse, destination_rse, activity) of waiting requests, find the limits which must be enforced
|
|
277
|
+
grouper = RequestGrouper()
|
|
278
|
+
for db_stat in db_stats:
|
|
279
|
+
account = db_stat.account
|
|
280
|
+
state = db_stat.state
|
|
281
|
+
counter = db_stat.counter
|
|
282
|
+
activity = db_stat.activity
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
dest_rse = rse_collection[db_stat.dest_rse_id].ensure_loaded(load_transfer_limits=True, load_name=True, load_columns=True)
|
|
286
|
+
except exception.RSENotFound:
|
|
287
|
+
logger(logging.INFO, "Destination RSE {} not found. Probably deleted.", db_stat.dest_rse_id)
|
|
288
|
+
continue
|
|
289
|
+
source_rse = None
|
|
290
|
+
if db_stat.source_rse_id:
|
|
291
|
+
try:
|
|
292
|
+
source_rse = rse_collection[db_stat.source_rse_id].ensure_loaded(load_transfer_limits=True, load_name=True, load_columns=True)
|
|
293
|
+
except exception.RSENotFound:
|
|
294
|
+
logger(logging.INFO, "Source RSE {} not found. Probably deleted.", db_stat.source_rse_id)
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
source_limits = list(applicable_rse_transfer_limits(activity=activity, source_rse=source_rse))
|
|
298
|
+
dest_limits = list(applicable_rse_transfer_limits(activity=activity, dest_rse=dest_rse))
|
|
299
|
+
limits = source_limits + dest_limits
|
|
300
|
+
|
|
301
|
+
if counter and (limits or state == RequestState.WAITING):
|
|
302
|
+
applicable_limits = []
|
|
303
|
+
for limit in limits:
|
|
304
|
+
limit_stat = limit_stats.setdefault(limit['id'], {})
|
|
305
|
+
applicable_limits.append(limit_stat)
|
|
306
|
+
|
|
307
|
+
if not limit_stat:
|
|
308
|
+
limit_stat.update({
|
|
309
|
+
'limit': limit,
|
|
310
|
+
'stat': {
|
|
311
|
+
'waiting': 0,
|
|
312
|
+
'active': 0,
|
|
313
|
+
'accounts': {},
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
if account is None:
|
|
318
|
+
# account == None results in SQL queries which doesn't filter on account at all.
|
|
319
|
+
# While account == null() explicitly filters on "account is NULL" in the database.
|
|
320
|
+
# Here we want the second case.
|
|
321
|
+
account = null()
|
|
322
|
+
|
|
323
|
+
stat = limit_stat['stat']
|
|
324
|
+
if account not in stat['accounts']:
|
|
325
|
+
stat['accounts'][account] = {'waiting': 0, 'active': 0}
|
|
326
|
+
|
|
327
|
+
if state == RequestState.WAITING:
|
|
328
|
+
stat['waiting'] += counter
|
|
329
|
+
stat['accounts'][account]['waiting'] += counter
|
|
330
|
+
else:
|
|
331
|
+
stat['active'] += counter
|
|
332
|
+
stat['accounts'][account]['active'] += counter
|
|
333
|
+
|
|
334
|
+
if state == RequestState.WAITING:
|
|
335
|
+
grouper.record_waiting_request_group(
|
|
336
|
+
source_rse=source_rse, # type: ignore
|
|
337
|
+
dest_rse=dest_rse,
|
|
338
|
+
activity=activity,
|
|
339
|
+
applicable_limits=applicable_limits,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Find the residual capacity in each of the limits
|
|
343
|
+
for limit_stat in limit_stats.values():
|
|
344
|
+
stat = limit_stat['stat']
|
|
345
|
+
limit = limit_stat['limit']
|
|
346
|
+
|
|
347
|
+
waiting = stat['waiting']
|
|
348
|
+
active = stat['active']
|
|
349
|
+
|
|
350
|
+
max_transfers = limit['max_transfers']
|
|
351
|
+
|
|
352
|
+
rse_expression = limit['rse_expression']
|
|
353
|
+
log_str = f'limit {"from" if limit["direction"] == "source" else "to"} {rse_expression} activity {limit["activity"]}'
|
|
354
|
+
|
|
355
|
+
if max_transfers is None:
|
|
356
|
+
# The limit was explicitly set to NULL. Release all waiting requests.
|
|
357
|
+
residual_capacity = math.inf
|
|
358
|
+
elif active < 0.8 * max_transfers:
|
|
359
|
+
residual_capacity = max_transfers - active
|
|
360
|
+
elif 0.8 * max_transfers <= active < max_transfers:
|
|
361
|
+
# Don't release requests yet. We desire to release transfers in bigger bulks than the currently available capacity.
|
|
362
|
+
logger(logging.DEBUG, "%s: will do nothing (active >= 0.8 * max_transfers)", log_str)
|
|
363
|
+
residual_capacity = 0
|
|
364
|
+
else: # active >= max_transfers
|
|
365
|
+
residual_capacity = 0
|
|
366
|
+
stat['residual_capacity'] = residual_capacity
|
|
367
|
+
|
|
368
|
+
activity = limit['activity'] or 'all_activities'
|
|
369
|
+
METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='residual_capacity').set(residual_capacity)
|
|
370
|
+
METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='max_transfers').set(max_transfers)
|
|
371
|
+
METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='active').set(active)
|
|
372
|
+
METRICS.gauge('rse_transfer_limits.{activity}.{rse}.{limit_attr}').labels(activity=activity, rse=rse_expression, limit_attr='waiting').set(waiting)
|
|
373
|
+
|
|
374
|
+
if waiting:
|
|
375
|
+
logger(logging.DEBUG, "%s: can release %s out of %s waiting requests", log_str, residual_capacity, waiting)
|
|
376
|
+
|
|
377
|
+
if waiting != limit['waitings'] or active != limit['transfers']:
|
|
378
|
+
set_transfer_limit_stats(limit['id'], waitings=waiting, transfers=active)
|
|
379
|
+
|
|
380
|
+
for account, to_release_for_account in _split_threshold_per_account(stat['accounts'], total_to_release=residual_capacity): # type: ignore (stat['accounts'] is not None)
|
|
381
|
+
stat['accounts'][account]['residual_capacity'] = to_release_for_account
|
|
382
|
+
|
|
383
|
+
release_groups = grouper.merged_groups()
|
|
384
|
+
return release_groups
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _split_threshold_per_account(
|
|
388
|
+
per_account_stats: dict["InternalAccount", dict[str, float]],
|
|
389
|
+
total_to_release: float
|
|
390
|
+
) -> Union[tuple[None, int], "Iterator[tuple[InternalAccount, float]]"]:
|
|
391
|
+
"""
|
|
392
|
+
Compute how many requests to release for each account. Try to achieve a fair share of transfers between accounts.
|
|
393
|
+
:param per_account_stats: a dict with how many active and waiting transfers each account has
|
|
394
|
+
:param total_to_release: the total threshold allowed to be released
|
|
395
|
+
:return: for each account, how many requests to release
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
if not per_account_stats:
|
|
399
|
+
return None, total_to_release
|
|
400
|
+
|
|
401
|
+
nr_accounts = len(per_account_stats)
|
|
402
|
+
remaining_to_release = total_to_release
|
|
403
|
+
remaining_accounts = nr_accounts
|
|
404
|
+
for account, account_stat in sorted(per_account_stats.items(), key=lambda i: i[1]['waiting']):
|
|
405
|
+
threshold_per_account = math.ceil(remaining_to_release / remaining_accounts)
|
|
406
|
+
|
|
407
|
+
waiting = account_stat['waiting']
|
|
408
|
+
to_release_for_account = min(waiting, threshold_per_account)
|
|
409
|
+
|
|
410
|
+
yield account, to_release_for_account
|
|
411
|
+
|
|
412
|
+
remaining_accounts -= 1
|
|
413
|
+
remaining_to_release -= to_release_for_account
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _combine_limits(
|
|
417
|
+
applicable_limits: list['LimitStatDict']
|
|
418
|
+
) -> tuple[float, str, Optional[int], Optional[int]]:
|
|
419
|
+
"""
|
|
420
|
+
Take multiple limits and combines them into one single (strictest) limit which
|
|
421
|
+
respects the constraints of each initial limits. This is to handle cases like:
|
|
422
|
+
- source rse only allows 5 transfers; destination allows 10 -> keep the stricter limit (here: 5 transfers)
|
|
423
|
+
- an RSE has multiple limits due to overlapping rse expressions -> also keep the stricter limit
|
|
424
|
+
|
|
425
|
+
Rules:
|
|
426
|
+
- for `to_release`: pick the minimum available residual capacity
|
|
427
|
+
- for `max_transfers` and 'volume': just pick the minimum
|
|
428
|
+
- prioritize `grouped_fifo` strategy over `fifo` (fifo being the default when not set)
|
|
429
|
+
- keep the closest deadline
|
|
430
|
+
"""
|
|
431
|
+
strategy_priorities = {
|
|
432
|
+
'grouped_fifo': 1,
|
|
433
|
+
'fifo': 2,
|
|
434
|
+
None: 3,
|
|
435
|
+
}
|
|
436
|
+
to_release = math.inf
|
|
437
|
+
max_transfers = None
|
|
438
|
+
strategy = 'fifo'
|
|
439
|
+
volume = None
|
|
440
|
+
deadline = None
|
|
441
|
+
for limit_stat in applicable_limits:
|
|
442
|
+
limit = limit_stat['limit']
|
|
443
|
+
stat = limit_stat['stat']
|
|
444
|
+
|
|
445
|
+
to_release = min(stat['residual_capacity'], to_release)
|
|
446
|
+
max_transfers = min(limit['max_transfers'], max_transfers, key=lambda x: x if x is not None else math.inf)
|
|
447
|
+
strategy = min(limit['strategy'], strategy, key=lambda x: strategy_priorities.get(x, math.inf))
|
|
448
|
+
volume = min(limit['volume'], volume, key=lambda x: x if x is not None else math.inf)
|
|
449
|
+
deadline = min(limit['deadline'], deadline, key=lambda x: x if x is not None else math.inf)
|
|
450
|
+
|
|
451
|
+
return to_release, strategy, volume, deadline
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _handle_requests(
|
|
455
|
+
release_groups: "ReleaseGroupsDict",
|
|
456
|
+
logger: "LoggerFunction"
|
|
457
|
+
) -> None:
|
|
458
|
+
"""
|
|
459
|
+
Release (set to queued state) waiting requests in groups defined by release_groups.
|
|
460
|
+
|
|
461
|
+
The same limit can be shared by multiple groups. Because of that, releasing requests
|
|
462
|
+
from one group can impact how many requests may be released in other groups subjected
|
|
463
|
+
to the same limit.
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
for (source_rse, dest_rse, activity), applicable_limits in release_groups.items():
|
|
467
|
+
|
|
468
|
+
# Skip if dest_rse is blocklisted for write or src_rse is blocklisted for read
|
|
469
|
+
if dest_rse and not dest_rse.columns['availability_write']:
|
|
470
|
+
continue
|
|
471
|
+
if source_rse and not source_rse.columns['availability_read']:
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
source_rse_id = source_rse.id if source_rse else None
|
|
475
|
+
dest_rse_id = dest_rse.id if dest_rse else None
|
|
476
|
+
|
|
477
|
+
log_str = (f' for activity "{activity}"' if activity else '') + \
|
|
478
|
+
(f' from rse {source_rse}' if source_rse else '') + \
|
|
479
|
+
(f' to rse {dest_rse}' if dest_rse else '')
|
|
480
|
+
|
|
481
|
+
to_release, strategy, volume, deadline = _combine_limits(applicable_limits)
|
|
482
|
+
if not to_release:
|
|
483
|
+
logger(logging.DEBUG, "no requests can be released%s", log_str)
|
|
484
|
+
total_released = 0
|
|
485
|
+
elif to_release == math.inf:
|
|
486
|
+
logger(logging.DEBUG, "will release all waiting requests%s", log_str)
|
|
487
|
+
total_released = release_all_waiting_requests(dest_rse_id=dest_rse_id, source_rse_id=source_rse_id, activity=activity)
|
|
488
|
+
elif strategy == 'grouped_fifo':
|
|
489
|
+
logger(logging.DEBUG, "will release %s remaining requests%s", to_release, log_str)
|
|
490
|
+
additional_kwargs = {}
|
|
491
|
+
if volume is not None:
|
|
492
|
+
additional_kwargs['volume'] = volume
|
|
493
|
+
if deadline is not None:
|
|
494
|
+
additional_kwargs['deadline'] = deadline
|
|
495
|
+
total_released = release_waiting_requests_grouped_fifo(
|
|
496
|
+
source_rse_id=source_rse_id,
|
|
497
|
+
dest_rse_id=dest_rse_id,
|
|
498
|
+
count=to_release,
|
|
499
|
+
**additional_kwargs,
|
|
500
|
+
)
|
|
501
|
+
else:
|
|
502
|
+
total_released = 0
|
|
503
|
+
to_release_for_account = {}
|
|
504
|
+
limits_by_account = {}
|
|
505
|
+
for limit_stat in applicable_limits:
|
|
506
|
+
acc_dict = limit_stat['stat']['accounts']
|
|
507
|
+
for account, account_limit in acc_dict.items():
|
|
508
|
+
to_release_for_account[account] = min(to_release_for_account.get(account, to_release), account_limit['residual_capacity']) # type: ignore (Issue with TypedDict.__getitem__)
|
|
509
|
+
limits_by_account.setdefault(account, []).append(account_limit)
|
|
510
|
+
|
|
511
|
+
for account, to_release_account in to_release_for_account.items():
|
|
512
|
+
if not to_release_account:
|
|
513
|
+
continue
|
|
514
|
+
|
|
515
|
+
logger(logging.DEBUG, 'releasing %s waiting requests%s%s', to_release_account, log_str, f' account {account}' if account is not None else '')
|
|
516
|
+
nb_released = release_waiting_requests_fifo(
|
|
517
|
+
source_rse_id=source_rse_id,
|
|
518
|
+
dest_rse_id=dest_rse_id,
|
|
519
|
+
count=to_release_account,
|
|
520
|
+
activity=activity,
|
|
521
|
+
account=account,
|
|
522
|
+
)
|
|
523
|
+
total_released += nb_released
|
|
524
|
+
|
|
525
|
+
for stat in limits_by_account[account]:
|
|
526
|
+
stat['residual_capacity'] -= nb_released
|
|
527
|
+
|
|
528
|
+
if total_released:
|
|
529
|
+
for limit_stat in applicable_limits:
|
|
530
|
+
rse_expression = limit_stat['limit']['rse_expression']
|
|
531
|
+
limit_stat['stat']['residual_capacity'] -= total_released
|
|
532
|
+
METRICS.counter('released_waiting_requests.{activity}.{rse}').labels(activity=activity, rse=rse_expression).inc(total_released)
|
|
@@ -0,0 +1,13 @@
|
|
|
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.
|
|
@@ -0,0 +1,101 @@
|
|
|
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 os
|
|
17
|
+
import socket
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
import rucio.db.sqla.util
|
|
23
|
+
from rucio.common import exception
|
|
24
|
+
from rucio.common.logging import setup_logging
|
|
25
|
+
from rucio.common.utils import get_thread_with_periodic_running_function
|
|
26
|
+
from rucio.core.did import create_reports
|
|
27
|
+
from rucio.core.heartbeat import die, live, sanity_check
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from types import FrameType
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
graceful_stop = threading.Event()
|
|
34
|
+
DAEMON_NAME = 'rucio-follower'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def aggregate_events(
|
|
38
|
+
once: bool = False
|
|
39
|
+
) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Collect all the events affecting the dids followed by the corresponding account.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
logging.info('event_aggregation: started')
|
|
45
|
+
|
|
46
|
+
hostname = socket.gethostname()
|
|
47
|
+
pid = os.getpid()
|
|
48
|
+
current_thread = threading.current_thread()
|
|
49
|
+
live(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
|
|
50
|
+
|
|
51
|
+
while not graceful_stop.is_set():
|
|
52
|
+
heartbeat = live(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
|
|
53
|
+
# Create a report of events and send a mail to the corresponding account.
|
|
54
|
+
start_time = time.time()
|
|
55
|
+
create_reports(total_workers=heartbeat['nr_threads'] - 1,
|
|
56
|
+
worker_number=heartbeat['assign_thread'])
|
|
57
|
+
logging.info('worker[%s/%s] took %s for creating reports' % (heartbeat['assign_thread'], heartbeat['nr_threads'] - 1, time.time() - start_time))
|
|
58
|
+
|
|
59
|
+
if once:
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
logging.info('follower: graceful stop requested')
|
|
63
|
+
die(executable=DAEMON_NAME, hostname=hostname, pid=pid, thread=current_thread)
|
|
64
|
+
logging.info('follower: graceful stop done')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def stop(signum: "Optional[int]" = None, frame: "Optional[FrameType]" = None) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Graceful exit.
|
|
70
|
+
"""
|
|
71
|
+
graceful_stop.set()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def run(
|
|
75
|
+
once: bool = False,
|
|
76
|
+
threads: int = 1
|
|
77
|
+
) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Starts up the follower threads
|
|
80
|
+
"""
|
|
81
|
+
setup_logging(process_name=DAEMON_NAME)
|
|
82
|
+
|
|
83
|
+
if rucio.db.sqla.util.is_old_db():
|
|
84
|
+
raise exception.DatabaseException('Database was not updated, daemon won\'t start')
|
|
85
|
+
|
|
86
|
+
hostname = socket.gethostname()
|
|
87
|
+
sanity_check(executable=DAEMON_NAME, hostname=hostname)
|
|
88
|
+
|
|
89
|
+
if once:
|
|
90
|
+
logging.info("executing one follower iteration only")
|
|
91
|
+
aggregate_events(once)
|
|
92
|
+
else:
|
|
93
|
+
logging.info("starting follower threads")
|
|
94
|
+
# Run the follower daemon thrice a day
|
|
95
|
+
thread_list = [get_thread_with_periodic_running_function(28800, aggregate_events, graceful_stop) for i in range(threads)]
|
|
96
|
+
[t.start() for t in thread_list]
|
|
97
|
+
|
|
98
|
+
logging.info("waiting for interrupts")
|
|
99
|
+
# Interruptible joins require a timeout.
|
|
100
|
+
while thread_list[0].is_alive():
|
|
101
|
+
[t.join(timeout=3.14) for t in thread_list]
|
|
@@ -0,0 +1,13 @@
|
|
|
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.
|