mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
550 lines
18 KiB
Rust
550 lines
18 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
sync::{Arc, OnceLock},
|
|
time::SystemTime,
|
|
};
|
|
|
|
use crate::bucket::bandwidth::monitor::Monitor;
|
|
use crate::disk::endpoint::Endpoint;
|
|
use crate::{
|
|
bucket::lifecycle::bucket_lifecycle_ops::{ExpiryState, GLOBAL_ExpiryState, GLOBAL_TransitionState, TransitionState},
|
|
bucket::metadata_sys::{BucketMetadataSys, get_global_bucket_metadata_sys},
|
|
bucket::replication::{DynReplicationPool, GLOBAL_REPLICATION_POOL, GLOBAL_REPLICATION_STATS, ReplicationStats},
|
|
config::{get_global_storage_class, set_global_storage_class, storageclass},
|
|
disk::{DiskAPI, DiskOption, DiskStore, new_disk},
|
|
error::Result,
|
|
layout::endpoints::{EndpointServerPools, SetupType},
|
|
runtime::global::{
|
|
GLOBAL_BOOT_TIME, GLOBAL_EventNotifier, GLOBAL_IsErasureSD, GLOBAL_LOCAL_DISK_ID_MAP, GLOBAL_LOCAL_DISK_MAP,
|
|
GLOBAL_LOCAL_DISK_SET_DRIVES, GLOBAL_LifecycleSys, GLOBAL_LocalNodeName, GLOBAL_RootDiskThreshold, GLOBAL_TierConfigMgr,
|
|
TypeLocalDiskSetDrives, get_global_bucket_monitor, get_global_deployment_id, get_global_endpoints,
|
|
get_global_endpoints_opt, get_global_lock_clients, get_global_region, get_global_tier_config_mgr, global_rustfs_port,
|
|
init_global_bucket_monitor, is_dist_erasure, is_erasure, is_first_cluster_node_local, resolve_object_store_handle,
|
|
set_global_deployment_id, set_global_lock_client, set_global_lock_clients, set_object_layer, update_erasure_type,
|
|
},
|
|
services::batch_processor::{GlobalBatchProcessors, get_global_processors},
|
|
services::event_notification::EventNotifier,
|
|
services::notification_sys::{NotificationSys, get_global_notification_sys},
|
|
services::tier::tier::TierConfigMgr,
|
|
store::ECStore,
|
|
};
|
|
use rustfs_common::{GLOBAL_LOCAL_NODE_NAME, GLOBAL_RUSTFS_ADDR, GLOBAL_RUSTFS_HOST};
|
|
use rustfs_concurrency::WorkloadAdmissionSnapshotProvider;
|
|
use rustfs_config::server_config::{Config, get_global_server_config, set_global_server_config};
|
|
use rustfs_io_metrics::internode_metrics::global_internode_metrics;
|
|
use rustfs_kms::{ObjectEncryptionService, get_global_encryption_service};
|
|
use rustfs_lock::client::LockClient;
|
|
use s3s::dto::BucketLifecycleConfiguration;
|
|
use s3s::region::Region;
|
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
|
use tonic::transport::Channel;
|
|
use uuid::Uuid;
|
|
|
|
#[cfg(test)]
|
|
const TEST_RPC_SECRET: &str = "test-rpc-secret";
|
|
|
|
pub(crate) type WorkloadSnapshotProviderRef = Arc<dyn WorkloadAdmissionSnapshotProvider + Send + Sync>;
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct LockRegistry {
|
|
clients: HashMap<String, Arc<dyn LockClient>>,
|
|
}
|
|
|
|
impl LockRegistry {
|
|
pub(crate) fn new(clients: HashMap<String, Arc<dyn LockClient>>) -> Self {
|
|
Self { clients }
|
|
}
|
|
|
|
pub(crate) fn clients_for_endpoints(&self, endpoints: &[Endpoint]) -> Vec<Arc<dyn LockClient>> {
|
|
let mut seen_hosts = HashSet::with_capacity(endpoints.len());
|
|
let mut clients = Vec::with_capacity(endpoints.len());
|
|
|
|
for endpoint in endpoints {
|
|
let host_port = endpoint.host_port();
|
|
if host_port.is_empty() || !seen_hosts.insert(host_port.clone()) {
|
|
continue;
|
|
}
|
|
|
|
if let Some(client) = self.clients.get(&host_port) {
|
|
clients.push(client.clone());
|
|
}
|
|
}
|
|
|
|
clients
|
|
}
|
|
}
|
|
|
|
static WORKLOAD_ADMISSION_SNAPSHOT_PROVIDER: OnceLock<WorkloadSnapshotProviderRef> = OnceLock::new();
|
|
|
|
pub(crate) fn set_workload_admission_snapshot_provider(
|
|
provider: WorkloadSnapshotProviderRef,
|
|
) -> std::result::Result<(), WorkloadSnapshotProviderRef> {
|
|
WORKLOAD_ADMISSION_SNAPSHOT_PROVIDER.set(provider)
|
|
}
|
|
|
|
pub(crate) fn workload_admission_snapshot_provider() -> Option<WorkloadSnapshotProviderRef> {
|
|
WORKLOAD_ADMISSION_SNAPSHOT_PROVIDER.get().cloned()
|
|
}
|
|
|
|
pub(crate) fn record_erasure_write_quorum_failure(stage: &'static str, dominant_error: &'static str) {
|
|
global_internode_metrics().record_erasure_write_quorum_failure(stage, dominant_error);
|
|
}
|
|
|
|
pub(crate) async fn object_encryption_service() -> Option<Arc<ObjectEncryptionService>> {
|
|
get_global_encryption_service().await
|
|
}
|
|
|
|
pub fn object_store_handle() -> Option<Arc<ECStore>> {
|
|
resolve_object_store_handle()
|
|
}
|
|
|
|
pub(crate) fn endpoint_pools() -> Option<EndpointServerPools> {
|
|
get_global_endpoints_opt()
|
|
}
|
|
|
|
pub(crate) fn endpoint_pools_or_default() -> EndpointServerPools {
|
|
get_global_endpoints()
|
|
}
|
|
|
|
pub(crate) fn endpoint_erasure_set_count() -> Option<usize> {
|
|
endpoint_pools().map(|endpoints| endpoints.es_count())
|
|
}
|
|
|
|
pub(crate) fn endpoint_pool_is_local(pool_index: usize) -> bool {
|
|
get_global_endpoints()
|
|
.as_ref()
|
|
.get(pool_index)
|
|
.is_some_and(|pool| pool.endpoints.as_ref().first().is_some_and(|endpoint| endpoint.is_local))
|
|
}
|
|
|
|
pub async fn first_cluster_node_is_local() -> bool {
|
|
is_first_cluster_node_local().await
|
|
}
|
|
|
|
pub async fn setup_is_erasure() -> bool {
|
|
is_erasure().await
|
|
}
|
|
|
|
pub(crate) async fn setup_is_dist_erasure() -> bool {
|
|
is_dist_erasure().await
|
|
}
|
|
|
|
pub async fn setup_is_erasure_sd() -> bool {
|
|
*GLOBAL_IsErasureSD.read().await
|
|
}
|
|
|
|
pub(crate) async fn current_setup_type() -> SetupType {
|
|
if setup_is_dist_erasure().await {
|
|
SetupType::DistErasure
|
|
} else if setup_is_erasure_sd().await {
|
|
SetupType::ErasureSD
|
|
} else if setup_is_erasure().await {
|
|
SetupType::Erasure
|
|
} else {
|
|
SetupType::Unknown
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn set_setup_type(setup_type: SetupType) {
|
|
update_erasure_type(setup_type).await;
|
|
}
|
|
|
|
pub(crate) async fn local_node_name() -> String {
|
|
GLOBAL_LOCAL_NODE_NAME.read().await.clone()
|
|
}
|
|
|
|
pub(crate) async fn set_local_node_name(node_name: String) {
|
|
*GLOBAL_LOCAL_NODE_NAME.write().await = node_name;
|
|
}
|
|
|
|
pub(crate) fn default_local_node_name() -> String {
|
|
GLOBAL_LocalNodeName.to_string()
|
|
}
|
|
|
|
pub(crate) fn rustfs_port() -> u16 {
|
|
global_rustfs_port()
|
|
}
|
|
|
|
pub(crate) async fn rustfs_host() -> String {
|
|
GLOBAL_RUSTFS_HOST.read().await.clone()
|
|
}
|
|
|
|
pub(crate) async fn rustfs_addr() -> String {
|
|
GLOBAL_RUSTFS_ADDR.read().await.clone()
|
|
}
|
|
|
|
pub(crate) fn boot_uptime_secs() -> u64 {
|
|
GLOBAL_BOOT_TIME
|
|
.get()
|
|
.and_then(|boot_time| SystemTime::now().duration_since(*boot_time).ok())
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
}
|
|
|
|
pub(crate) async fn ensure_boot_time() {
|
|
GLOBAL_BOOT_TIME.get_or_init(|| async { SystemTime::now() }).await;
|
|
}
|
|
|
|
pub(crate) async fn scanner_init_time() -> Option<chrono::DateTime<chrono::Utc>> {
|
|
rustfs_common::get_global_init_time().await
|
|
}
|
|
|
|
pub(crate) async fn root_disk_threshold_for_erasure_disk() -> Option<u64> {
|
|
if *GLOBAL_IsErasureSD.read().await {
|
|
None
|
|
} else {
|
|
Some(*GLOBAL_RootDiskThreshold.read().await)
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn cached_node_channel(addr: &str) -> Option<Channel> {
|
|
rustfs_common::cached_connection(addr).await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) async fn cache_test_node_channel(addr: String, channel: Channel) {
|
|
rustfs_common::cache_connection(addr, channel).await;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) async fn test_node_channel_is_cached(addr: &str) -> bool {
|
|
rustfs_common::has_cached_connection(addr).await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn ensure_test_rpc_secret() {
|
|
let _ = rustfs_credentials::GLOBAL_RUSTFS_RPC_SECRET.set(TEST_RPC_SECRET.to_owned());
|
|
}
|
|
|
|
pub(crate) fn storage_class_parity(storage_class: Option<&str>) -> Option<usize> {
|
|
get_global_storage_class().and_then(|sc| sc.get_parity_for_sc(storage_class.unwrap_or_default()))
|
|
}
|
|
|
|
pub(crate) fn backend_storage_class_parities(default_standard_parity: usize) -> (Option<usize>, Option<usize>) {
|
|
if let Some(sc) = get_global_storage_class() {
|
|
let standard = sc
|
|
.get_parity_for_sc(storageclass::CLASS_STANDARD)
|
|
.or(Some(default_standard_parity));
|
|
let reduced_redundancy = sc.get_parity_for_sc(storageclass::RRS);
|
|
(standard, reduced_redundancy)
|
|
} else {
|
|
(Some(default_standard_parity), None)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn storage_class_should_inline(shard_size: i64, versioned: bool) -> bool {
|
|
get_global_storage_class().is_some_and(|sc| sc.should_inline(shard_size, versioned))
|
|
}
|
|
|
|
pub(crate) fn deployment_upload_id(upload_id: &str) -> String {
|
|
base64_simd::URL_SAFE_NO_PAD
|
|
.encode_to_string(format!("{}.{}", get_global_deployment_id().unwrap_or_default(), upload_id).as_bytes())
|
|
}
|
|
|
|
pub(crate) fn deployment_id() -> Option<String> {
|
|
get_global_deployment_id()
|
|
}
|
|
|
|
pub(crate) fn replication_pool() -> Option<Arc<DynReplicationPool>> {
|
|
GLOBAL_REPLICATION_POOL.get().cloned()
|
|
}
|
|
|
|
pub(crate) fn replication_stats() -> Option<Arc<ReplicationStats>> {
|
|
GLOBAL_REPLICATION_STATS.get().cloned()
|
|
}
|
|
|
|
pub(crate) fn replication_runtime_initialized() -> bool {
|
|
GLOBAL_REPLICATION_STATS.get().is_some() && GLOBAL_REPLICATION_POOL.get().is_some()
|
|
}
|
|
|
|
pub(crate) fn ensure_deployment_id(deployment_id: Uuid) {
|
|
if get_global_deployment_id().is_none() {
|
|
set_global_deployment_id(deployment_id);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn global_lock_manager() -> Arc<rustfs_lock::GlobalLockManager> {
|
|
rustfs_lock::get_global_lock_manager()
|
|
}
|
|
|
|
pub(crate) fn global_lock_clients() -> Option<&'static HashMap<String, Arc<dyn LockClient>>> {
|
|
get_global_lock_clients()
|
|
}
|
|
|
|
pub(crate) fn lock_registry() -> Option<LockRegistry> {
|
|
global_lock_clients()
|
|
.map(|clients| LockRegistry::new(clients.iter().map(|(host, client)| (host.clone(), client.clone())).collect()))
|
|
}
|
|
|
|
pub(crate) fn set_primary_lock_client(client: Arc<dyn LockClient>) -> std::result::Result<(), Arc<dyn LockClient>> {
|
|
set_global_lock_client(client)
|
|
}
|
|
|
|
pub(crate) fn set_lock_clients(
|
|
clients: HashMap<String, Arc<dyn LockClient>>,
|
|
) -> std::result::Result<(), HashMap<String, Arc<dyn LockClient>>> {
|
|
set_global_lock_clients(clients)
|
|
}
|
|
|
|
pub(crate) async fn publish_object_store(store: Arc<ECStore>) {
|
|
set_object_layer(store).await;
|
|
}
|
|
|
|
pub(crate) fn notification_sys() -> Option<&'static NotificationSys> {
|
|
get_global_notification_sys()
|
|
}
|
|
|
|
pub(crate) fn bucket_metadata_sys() -> Option<Arc<RwLock<BucketMetadataSys>>> {
|
|
get_global_bucket_metadata_sys()
|
|
}
|
|
|
|
pub(crate) fn region() -> Option<Region> {
|
|
get_global_region()
|
|
}
|
|
|
|
pub(crate) fn server_config() -> Option<Config> {
|
|
get_global_server_config()
|
|
}
|
|
|
|
pub(crate) fn set_server_config(config: Config) {
|
|
set_global_server_config(config);
|
|
}
|
|
|
|
pub(crate) fn storage_class_config() -> Option<storageclass::Config> {
|
|
get_global_storage_class()
|
|
}
|
|
|
|
pub(crate) fn set_storage_class_config(config: storageclass::Config) {
|
|
set_global_storage_class(config);
|
|
}
|
|
|
|
pub(crate) fn batch_processors() -> &'static GlobalBatchProcessors {
|
|
get_global_processors()
|
|
}
|
|
|
|
pub fn global_tier_config_mgr() -> Arc<RwLock<TierConfigMgr>> {
|
|
get_global_tier_config_mgr()
|
|
}
|
|
|
|
pub(crate) async fn bucket_lifecycle_config(bucket: &str) -> Option<BucketLifecycleConfiguration> {
|
|
GLOBAL_LifecycleSys.get(bucket).await
|
|
}
|
|
|
|
pub(crate) fn delete_bucket_monitor_entry(bucket: &str) {
|
|
if let Some(monitor) = get_global_bucket_monitor() {
|
|
monitor.delete_bucket(bucket);
|
|
}
|
|
}
|
|
|
|
pub fn bucket_monitor() -> Option<Arc<Monitor>> {
|
|
get_global_bucket_monitor()
|
|
}
|
|
|
|
pub async fn local_disk_map_read() -> RwLockReadGuard<'static, HashMap<String, Option<DiskStore>>> {
|
|
GLOBAL_LOCAL_DISK_MAP.read().await
|
|
}
|
|
|
|
pub(crate) fn init_bucket_monitor_for_current_endpoints() {
|
|
let num_nodes = get_global_endpoints().get_nodes().len().try_into().unwrap_or(u64::MAX);
|
|
init_global_bucket_monitor(num_nodes);
|
|
}
|
|
|
|
pub(crate) fn local_disk_map_handle() -> Arc<RwLock<HashMap<String, Option<DiskStore>>>> {
|
|
GLOBAL_LOCAL_DISK_MAP.clone()
|
|
}
|
|
|
|
pub(crate) fn local_disk_id_map_handle() -> Arc<RwLock<HashMap<Uuid, String>>> {
|
|
GLOBAL_LOCAL_DISK_ID_MAP.clone()
|
|
}
|
|
|
|
pub(crate) fn local_disk_set_drives_handle() -> Arc<RwLock<TypeLocalDiskSetDrives>> {
|
|
GLOBAL_LOCAL_DISK_SET_DRIVES.clone()
|
|
}
|
|
|
|
pub(crate) fn tier_config_mgr_handle() -> Arc<RwLock<TierConfigMgr>> {
|
|
GLOBAL_TierConfigMgr.clone()
|
|
}
|
|
|
|
pub fn expiry_state_handle() -> Arc<RwLock<ExpiryState>> {
|
|
GLOBAL_ExpiryState.clone()
|
|
}
|
|
|
|
pub fn transition_state_handle() -> Arc<TransitionState> {
|
|
GLOBAL_TransitionState.clone()
|
|
}
|
|
|
|
pub(crate) fn event_notifier_handle() -> Arc<RwLock<EventNotifier>> {
|
|
GLOBAL_EventNotifier.clone()
|
|
}
|
|
|
|
pub(crate) async fn local_disk_by_path(path: &str) -> Option<DiskStore> {
|
|
GLOBAL_LOCAL_DISK_MAP.read().await.get(path).cloned().flatten()
|
|
}
|
|
|
|
pub(crate) async fn local_disk_path_by_id(disk_id: &Uuid) -> Option<String> {
|
|
GLOBAL_LOCAL_DISK_ID_MAP.read().await.get(disk_id).cloned()
|
|
}
|
|
|
|
pub(crate) async fn record_local_disk_id(disk_id: Uuid, endpoint: String) {
|
|
GLOBAL_LOCAL_DISK_ID_MAP.write().await.insert(disk_id, endpoint);
|
|
}
|
|
|
|
pub(crate) async fn replace_local_disk_id(previous: Option<Uuid>, current: Option<Uuid>, endpoint: String) {
|
|
let mut disk_id_map = GLOBAL_LOCAL_DISK_ID_MAP.write().await;
|
|
if let Some(previous_id) = previous {
|
|
disk_id_map.remove(&previous_id);
|
|
}
|
|
if let Some(current_id) = current {
|
|
disk_id_map.insert(current_id, endpoint);
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn record_local_disks(disks: Vec<DiskStore>) {
|
|
let mut global_local_disk_map = GLOBAL_LOCAL_DISK_MAP.write().await;
|
|
for disk in disks {
|
|
let path = disk.endpoint().to_string();
|
|
global_local_disk_map.insert(path, Some(disk.clone()));
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn local_disk_set_drive(pool_idx: usize, set_idx: usize, disk_idx: usize) -> Option<DiskStore> {
|
|
GLOBAL_LOCAL_DISK_SET_DRIVES.read().await[pool_idx][set_idx][disk_idx].clone()
|
|
}
|
|
|
|
pub(crate) async fn local_disk_for_endpoint(endpoint: &Endpoint) -> Option<DiskStore> {
|
|
let global_set_drives = GLOBAL_LOCAL_DISK_SET_DRIVES.read().await;
|
|
if global_set_drives.is_empty() {
|
|
return GLOBAL_LOCAL_DISK_MAP
|
|
.read()
|
|
.await
|
|
.get(&endpoint.to_string())
|
|
.cloned()
|
|
.unwrap_or(None);
|
|
}
|
|
|
|
let pool_idx = usize::try_from(endpoint.pool_idx).ok()?;
|
|
let set_idx = usize::try_from(endpoint.set_idx).ok()?;
|
|
let disk_idx = usize::try_from(endpoint.disk_idx).ok()?;
|
|
|
|
global_set_drives
|
|
.get(pool_idx)
|
|
.and_then(|sets| sets.get(set_idx))
|
|
.and_then(|disks| disks.get(disk_idx))
|
|
.cloned()
|
|
.unwrap_or(None)
|
|
}
|
|
|
|
pub(crate) async fn local_disk_paths() -> Vec<String> {
|
|
GLOBAL_LOCAL_DISK_MAP.read().await.keys().cloned().collect()
|
|
}
|
|
|
|
pub(crate) async fn local_disks() -> Vec<DiskStore> {
|
|
GLOBAL_LOCAL_DISK_MAP
|
|
.read()
|
|
.await
|
|
.values()
|
|
.filter_map(|v| v.as_ref().cloned())
|
|
.collect()
|
|
}
|
|
|
|
pub(crate) async fn local_disk_entries() -> Vec<Option<DiskStore>> {
|
|
GLOBAL_LOCAL_DISK_MAP.read().await.values().cloned().collect()
|
|
}
|
|
|
|
pub(crate) async fn initialize_local_disk_maps(endpoint_pools: EndpointServerPools, opt: &DiskOption) -> Result<()> {
|
|
let mut global_set_drives = GLOBAL_LOCAL_DISK_SET_DRIVES.write().await;
|
|
for pool_eps in endpoint_pools.as_ref().iter() {
|
|
let mut set_count_drives = Vec::with_capacity(pool_eps.set_count);
|
|
for _ in 0..pool_eps.set_count {
|
|
set_count_drives.push(vec![None; pool_eps.drives_per_set]);
|
|
}
|
|
|
|
global_set_drives.push(set_count_drives);
|
|
}
|
|
|
|
let mut global_local_disk_map = GLOBAL_LOCAL_DISK_MAP.write().await;
|
|
|
|
for pool_eps in endpoint_pools.as_ref().iter() {
|
|
for ep in pool_eps.endpoints.as_ref().iter() {
|
|
if !ep.is_local {
|
|
continue;
|
|
}
|
|
|
|
let disk = new_disk(ep, opt).await?;
|
|
let path = disk.endpoint().to_string();
|
|
let pool_idx = usize::try_from(ep.pool_idx).map_err(|err| {
|
|
crate::error::Error::other(format!("store init failed to convert pool index `{}`: {err}", ep.pool_idx))
|
|
})?;
|
|
let set_idx = usize::try_from(ep.set_idx).map_err(|err| {
|
|
crate::error::Error::other(format!("store init failed to convert set index `{}`: {err}", ep.set_idx))
|
|
})?;
|
|
let disk_idx = usize::try_from(ep.disk_idx).map_err(|err| {
|
|
crate::error::Error::other(format!("store init failed to convert disk index `{}`: {err}", ep.disk_idx))
|
|
})?;
|
|
|
|
global_local_disk_map.insert(path, Some(disk.clone()));
|
|
global_set_drives[pool_idx][set_idx][disk_idx] = Some(disk.clone());
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn init_tier_config_mgr(store: Arc<ECStore>) -> Result<()> {
|
|
GLOBAL_TierConfigMgr.write().await.init(store).await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::LockRegistry;
|
|
use crate::disk::endpoint::Endpoint;
|
|
use rustfs_lock::{LocalClient, LockClient};
|
|
use std::{collections::HashMap, sync::Arc};
|
|
|
|
fn url_endpoint(raw: &str) -> Endpoint {
|
|
Endpoint {
|
|
url: url::Url::parse(raw).expect("test endpoint url"),
|
|
is_local: false,
|
|
pool_idx: 0,
|
|
set_idx: 0,
|
|
disk_idx: 0,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn lock_registry_selects_unique_clients_in_endpoint_order() {
|
|
let client_a: Arc<dyn LockClient> = Arc::new(LocalClient::new());
|
|
let client_b: Arc<dyn LockClient> = Arc::new(LocalClient::new());
|
|
let registry = LockRegistry::new(HashMap::from([
|
|
("node-a:9000".to_string(), client_a.clone()),
|
|
("node-b:9000".to_string(), client_b.clone()),
|
|
]));
|
|
let endpoints = vec![
|
|
url_endpoint("http://node-a:9000/data-a"),
|
|
url_endpoint("http://node-a:9000/data-b"),
|
|
url_endpoint("http://node-missing:9000/data"),
|
|
url_endpoint("http://node-b:9000/data"),
|
|
];
|
|
|
|
let clients = registry.clients_for_endpoints(&endpoints);
|
|
|
|
assert_eq!(clients.len(), 2);
|
|
assert!(Arc::ptr_eq(&clients[0], &client_a));
|
|
assert!(Arc::ptr_eq(&clients[1], &client_b));
|
|
}
|
|
}
|