mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 00:26:53 +00:00
@@ -1,5 +1,5 @@
|
||||
use s3s::{S3Error, S3ErrorCode};
|
||||
use std::{cmp::Ordering, env, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
@@ -7,17 +7,30 @@ use tokio::{
|
||||
},
|
||||
time::interval,
|
||||
};
|
||||
use tracing::info;
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
config::RUSTFS_CONFIG_PREFIX, disk::{endpoint::Endpoint, error::DiskError, DiskAPI, DiskInfoOptions, BUCKET_META_PREFIX, RUSTFS_META_BUCKET}, error::{Error, Result}, global::{GLOBAL_BackgroundHealRoutine, GLOBAL_BackgroundHealState, GLOBAL_LOCAL_DISK_MAP}, heal::{data_usage::{DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT}, data_usage_cache::DataUsageCache, heal_commands::{init_healing_tracker, load_healing_tracker}, heal_ops::NOP_HEAL}, new_object_layer_fn, store::get_disk_via_endpoint, store_api::{BucketInfo, BucketOptions, StorageAPI}, utils::path::{path_join, SLASH_SEPARATOR}
|
||||
};
|
||||
|
||||
use super::{
|
||||
heal_commands::{HealOpts, HealResultItem},
|
||||
heal_ops::{new_bg_heal_sequence, HealSequence},
|
||||
};
|
||||
use crate::heal::error::ERR_RETRY_HEALING;
|
||||
use crate::{
|
||||
config::RUSTFS_CONFIG_PREFIX,
|
||||
disk::{endpoint::Endpoint, error::DiskError, DiskAPI, DiskInfoOptions, BUCKET_META_PREFIX, RUSTFS_META_BUCKET},
|
||||
error::{Error, Result},
|
||||
global::{GLOBAL_BackgroundHealRoutine, GLOBAL_BackgroundHealState, GLOBAL_LOCAL_DISK_MAP},
|
||||
heal::{
|
||||
data_usage::{DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT},
|
||||
data_usage_cache::DataUsageCache,
|
||||
heal_commands::{init_healing_tracker, load_healing_tracker},
|
||||
heal_ops::NOP_HEAL,
|
||||
},
|
||||
new_object_layer_fn,
|
||||
store::get_disk_via_endpoint,
|
||||
store_api::{BucketInfo, BucketOptions, StorageAPI},
|
||||
utils::path::{path_join, SLASH_SEPARATOR},
|
||||
};
|
||||
|
||||
pub static DEFAULT_MONITOR_NEW_DISK_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
@@ -30,6 +43,9 @@ pub async fn init_auto_heal() {
|
||||
.await
|
||||
.push_heal_local_disks(&get_local_disks_to_heal().await)
|
||||
.await;
|
||||
tokio::spawn(async {
|
||||
monitor_local_disks_and_heal().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,10 +115,27 @@ async fn monitor_local_disks_and_heal() {
|
||||
for disk in heal_disks.into_ref().iter() {
|
||||
let disk_clone = disk.clone();
|
||||
tokio::spawn(async move {
|
||||
GLOBAL_BackgroundHealState.write().await.set_disk_healing_status(disk_clone.clone(), true).await;
|
||||
|
||||
GLOBAL_BackgroundHealState
|
||||
.write()
|
||||
.await
|
||||
.set_disk_healing_status(disk_clone.clone(), true)
|
||||
.await;
|
||||
if heal_fresh_disk(&disk_clone).await.is_err() {
|
||||
GLOBAL_BackgroundHealState
|
||||
.write()
|
||||
.await
|
||||
.set_disk_healing_status(disk_clone.clone(), false)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
GLOBAL_BackgroundHealState
|
||||
.write()
|
||||
.await
|
||||
.pop_heal_local_disks(&[disk_clone])
|
||||
.await;
|
||||
});
|
||||
}
|
||||
interval.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,16 +143,20 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
let (pool_idx, set_idx) = (endpoint.pool_idx.unwrap(), endpoint.disk_idx.unwrap());
|
||||
let disk = match get_disk_via_endpoint(endpoint).await {
|
||||
Some(disk) => disk,
|
||||
None => return Err(Error::from_string(format!("Unexpected error disk must be initialized by now after formatting: {}", endpoint.to_string()))),
|
||||
None => {
|
||||
return Err(Error::from_string(format!(
|
||||
"Unexpected error disk must be initialized by now after formatting: {}",
|
||||
endpoint
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = disk.disk_info(&DiskInfoOptions::default()).await {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::DriveIsRoot) => {
|
||||
return Ok(());
|
||||
},
|
||||
Some(DiskError::UnformattedDisk) => {
|
||||
},
|
||||
}
|
||||
Some(DiskError::UnformattedDisk) => {}
|
||||
_ => {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -134,14 +171,21 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
info!("Unable to load healing tracker on '{}': {}, re-initializing..", disk.to_string(), err.to_string());
|
||||
},
|
||||
info!(
|
||||
"Unable to load healing tracker on '{}': {}, re-initializing..",
|
||||
disk.to_string(),
|
||||
err.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
init_healing_tracker(disk.clone(), &Uuid::new_v4().to_string()).await?
|
||||
}
|
||||
};
|
||||
|
||||
info!("Healing drive '{}' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint.to_string());
|
||||
info!(
|
||||
"Healing drive '{}' - 'mc admin heal alias/ --verbose' to check the current status.",
|
||||
endpoint.to_string()
|
||||
);
|
||||
|
||||
let layer = new_object_layer_fn();
|
||||
let lock = layer.read().await;
|
||||
@@ -151,11 +195,15 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
};
|
||||
let mut buckets = store.list_bucket(&BucketOptions::default()).await?;
|
||||
buckets.push(BucketInfo {
|
||||
name: path_join(&[PathBuf::from(RUSTFS_META_BUCKET), PathBuf::from(RUSTFS_CONFIG_PREFIX)]).to_string_lossy().to_string(),
|
||||
name: path_join(&[PathBuf::from(RUSTFS_META_BUCKET), PathBuf::from(RUSTFS_CONFIG_PREFIX)])
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
buckets.push(BucketInfo {
|
||||
name: path_join(&[PathBuf::from(RUSTFS_META_BUCKET), PathBuf::from(BUCKET_META_PREFIX)]).to_string_lossy().to_string(),
|
||||
name: path_join(&[PathBuf::from(RUSTFS_META_BUCKET), PathBuf::from(BUCKET_META_PREFIX)])
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -170,55 +218,123 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
}
|
||||
});
|
||||
|
||||
let mut cache = match DataUsageCache::load(&store.pools[pool_idx].disk_set[set_idx], DATA_USAGE_CACHE_NAME).await {
|
||||
Ok(cache) => {
|
||||
let data_usage_info = cache.dui(DATA_USAGE_ROOT, &Vec::new());
|
||||
tracker.objects_total_count = data_usage_info.objects_total_count;
|
||||
tracker.objects_total_size = data_usage_info.objects_total_size;
|
||||
cache
|
||||
},
|
||||
Err(_) => DataUsageCache::default()
|
||||
if let Ok(cache) = DataUsageCache::load(&store.pools[pool_idx].disk_set[set_idx], DATA_USAGE_CACHE_NAME).await {
|
||||
let data_usage_info = cache.dui(DATA_USAGE_ROOT, &Vec::new());
|
||||
tracker.objects_total_count = data_usage_info.objects_total_count;
|
||||
tracker.objects_total_size = data_usage_info.objects_total_size;
|
||||
};
|
||||
|
||||
let location = disk.get_disk_location();
|
||||
tracker.set_queue_buckets(&buckets).await;
|
||||
tracker.save().await?;
|
||||
|
||||
// store.pools[pool_idx].disk_set[set_idx].
|
||||
todo!()
|
||||
let tracker = Arc::new(RwLock::new(tracker));
|
||||
let qb = tracker.read().await.queue_buckets.clone();
|
||||
store.pools[pool_idx].disk_set[set_idx]
|
||||
.clone()
|
||||
.heal_erasure_set(&qb, tracker.clone())
|
||||
.await?;
|
||||
let mut tracker_w = tracker.write().await;
|
||||
if tracker_w.items_failed > 0 && tracker_w.retry_attempts < 4 {
|
||||
tracker_w.retry_attempts += 1;
|
||||
tracker_w.reset_healing().await;
|
||||
if let Err(err) = tracker_w.update().await {
|
||||
info!("update tracker failed: {}", err.to_string());
|
||||
}
|
||||
return Err(Error::from_string(ERR_RETRY_HEALING));
|
||||
}
|
||||
|
||||
if tracker_w.items_failed > 0 {
|
||||
info!(
|
||||
"Healing of drive '{}' is incomplete, retried {} times (healed: {}, skipped: {}, failed: {}).",
|
||||
disk.to_string(),
|
||||
tracker_w.retry_attempts,
|
||||
tracker_w.items_healed,
|
||||
tracker_w.item_skipped,
|
||||
tracker_w.items_failed
|
||||
);
|
||||
} else if tracker_w.retry_attempts > 0 {
|
||||
info!(
|
||||
"Healing of drive '{}' is incomplete, retried {} times (healed: {}, skipped: {}).",
|
||||
disk.to_string(),
|
||||
tracker_w.retry_attempts,
|
||||
tracker_w.items_healed,
|
||||
tracker_w.item_skipped
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Healing of drive '{}' is finished (healed: {}, skipped: {}).",
|
||||
disk.to_string(),
|
||||
tracker_w.items_healed,
|
||||
tracker_w.item_skipped
|
||||
);
|
||||
}
|
||||
|
||||
if tracker_w.heal_id.is_empty() {
|
||||
if let Err(err) = tracker_w.delete().await {
|
||||
error!("delete tracker failed: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
let layer = new_object_layer_fn();
|
||||
let lock = layer.read().await;
|
||||
let store = match lock.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::from(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()))),
|
||||
};
|
||||
let disks = store.get_disks(pool_idx, set_idx).await?;
|
||||
for disk in disks.into_iter() {
|
||||
if disk.is_none() {
|
||||
continue;
|
||||
}
|
||||
let mut tracker = match load_healing_tracker(&disk).await {
|
||||
Ok(tracker) => tracker,
|
||||
Err(err) => {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) => {}
|
||||
_ => {
|
||||
info!("Unable to load healing tracker on '{:?}': {}, re-initializing..", disk, err.to_string());
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if tracker.heal_id == tracker_w.heal_id {
|
||||
tracker.finished = true;
|
||||
tracker.update().await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct HealTask {
|
||||
pub bucket: String,
|
||||
pub object: String,
|
||||
pub version_id: String,
|
||||
pub opts: HealOpts,
|
||||
pub resp_tx: Option<Arc<Sender<HealResult>>>,
|
||||
pub resp_rx: Option<Arc<Receiver<HealResult>>>,
|
||||
pub resp_tx: Option<Sender<HealResult>>,
|
||||
pub resp_rx: Option<Receiver<HealResult>>,
|
||||
}
|
||||
|
||||
impl HealTask {
|
||||
pub fn new(bucket: &str, object: &str, version_id: &str, opts: &HealOpts) -> Self {
|
||||
let (tx, rx) = mpsc::channel(10);
|
||||
Self {
|
||||
bucket: bucket.to_string(),
|
||||
object: object.to_string(),
|
||||
version_id: version_id.to_string(),
|
||||
opts: opts.clone(),
|
||||
resp_tx: Some(tx.into()),
|
||||
resp_rx: Some(rx.into()),
|
||||
opts: *opts,
|
||||
resp_tx: None,
|
||||
resp_rx: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HealResult {
|
||||
pub result: HealResultItem,
|
||||
_err: Option<Error>,
|
||||
pub err: Option<Error>,
|
||||
}
|
||||
|
||||
pub struct HealRoutine {
|
||||
tasks_tx: Sender<HealTask>,
|
||||
pub tasks_tx: Sender<HealTask>,
|
||||
tasks_rx: Receiver<HealTask>,
|
||||
workers: usize,
|
||||
}
|
||||
@@ -265,13 +381,10 @@ impl HealRoutine {
|
||||
let lock = layer.read().await;
|
||||
let store = lock.as_ref().expect("Not init");
|
||||
if task.object.is_empty() {
|
||||
match store
|
||||
.heal_object(&task.bucket, &task.object, &task.version_id, &task.opts)
|
||||
.await
|
||||
{
|
||||
Ok((res, err)) => {
|
||||
match store.heal_bucket(&task.bucket, &task.opts).await {
|
||||
Ok(res) => {
|
||||
d_res = res;
|
||||
d_err = err;
|
||||
d_err = None;
|
||||
}
|
||||
Err(err) => d_err = Some(err),
|
||||
}
|
||||
@@ -292,7 +405,7 @@ impl HealRoutine {
|
||||
let _ = resp_tx
|
||||
.send(HealResult {
|
||||
result: d_res,
|
||||
_err: d_err,
|
||||
err: d_err,
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
@@ -325,5 +438,5 @@ async fn heal_disk_format(opts: HealOpts) -> Result<(HealResultItem, Option<Erro
|
||||
if err.is_some() {
|
||||
return Ok((HealResultItem::default(), err));
|
||||
}
|
||||
return Ok((res, err));
|
||||
Ok((res, err))
|
||||
}
|
||||
|
||||
@@ -115,23 +115,27 @@ async fn run_data_scanner() {
|
||||
let mut buf = read_config(store, &DATA_USAGE_BLOOM_NAME_PATH)
|
||||
.await
|
||||
.map_or(Vec::new(), |buf| buf);
|
||||
if buf.len() == 8 {
|
||||
cycle_info.next = match Cursor::new(buf).read_u64::<LittleEndian>() {
|
||||
Ok(buf) => buf,
|
||||
Err(_) => {
|
||||
error!("can not decode DATA_USAGE_BLOOM_NAME_PATH");
|
||||
return;
|
||||
}
|
||||
};
|
||||
} else if buf.len() > 8 {
|
||||
cycle_info.next = match Cursor::new(buf[..8].to_vec()).read_u64::<LittleEndian>() {
|
||||
Ok(buf) => buf,
|
||||
Err(_) => {
|
||||
error!("can not decode DATA_USAGE_BLOOM_NAME_PATH");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = cycle_info.unmarshal_msg(&buf.split_off(8));
|
||||
match buf.len().cmp(&8) {
|
||||
std::cmp::Ordering::Less => {}
|
||||
std::cmp::Ordering::Equal => {
|
||||
cycle_info.next = match Cursor::new(buf).read_u64::<LittleEndian>() {
|
||||
Ok(buf) => buf,
|
||||
Err(_) => {
|
||||
error!("can not decode DATA_USAGE_BLOOM_NAME_PATH");
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
cycle_info.next = match Cursor::new(buf[..8].to_vec()).read_u64::<LittleEndian>() {
|
||||
Ok(buf) => buf,
|
||||
Err(_) => {
|
||||
error!("can not decode DATA_USAGE_BLOOM_NAME_PATH");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = cycle_info.unmarshal_msg(&buf.split_off(8));
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
@@ -377,11 +381,7 @@ pub struct ScannerItem {
|
||||
|
||||
impl ScannerItem {
|
||||
pub fn transform_meda_dir(&mut self) {
|
||||
let split = self
|
||||
.prefix
|
||||
.split(SLASH_SEPARATOR)
|
||||
.map(|s| PathBuf::from(s))
|
||||
.collect::<Vec<_>>();
|
||||
let split = self.prefix.split(SLASH_SEPARATOR).map(PathBuf::from).collect::<Vec<_>>();
|
||||
if split.len() > 1 {
|
||||
self.prefix = path_join(&split[0..split.len() - 1]).to_string_lossy().to_string();
|
||||
} else {
|
||||
@@ -700,13 +700,14 @@ impl FolderScanner {
|
||||
// Scan existing...
|
||||
for folder in existing_folders.iter() {
|
||||
let h = hash_path(&folder.name);
|
||||
if !into.compacted && self.old_cache.is_compacted(&h) {
|
||||
if !h.mod_(self.old_cache.info.next_cycle, DATA_USAGE_UPDATE_DIR_CYCLES) {
|
||||
self.new_cache
|
||||
.copy_with_children(&self.old_cache, &h, &Some(folder.parent.clone()));
|
||||
into.add_child(&h);
|
||||
continue;
|
||||
}
|
||||
if !into.compacted
|
||||
&& self.old_cache.is_compacted(&h)
|
||||
&& !h.mod_(self.old_cache.info.next_cycle, DATA_USAGE_UPDATE_DIR_CYCLES)
|
||||
{
|
||||
self.new_cache
|
||||
.copy_with_children(&self.old_cache, &h, &Some(folder.parent.clone()));
|
||||
into.add_child(&h);
|
||||
continue;
|
||||
}
|
||||
(self.update_current_path)(&folder.name).await;
|
||||
scan(folder, into, self).await;
|
||||
@@ -748,7 +749,7 @@ impl FolderScanner {
|
||||
(self.update_current_path)(k).await;
|
||||
|
||||
if bucket != resolver.bucket {
|
||||
let _ = bg_seq
|
||||
bg_seq
|
||||
.clone()
|
||||
.write()
|
||||
.await
|
||||
@@ -841,7 +842,7 @@ impl FolderScanner {
|
||||
}
|
||||
} else {
|
||||
let mut w = found_objs_clone.write().await;
|
||||
*w = *w || true;
|
||||
*w = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -866,10 +867,13 @@ impl FolderScanner {
|
||||
{
|
||||
Ok(_) => {
|
||||
success_versions += 1;
|
||||
|
||||
let mut w = found_objs_clone.write().await;
|
||||
*w = *w || true;
|
||||
*w = true;
|
||||
}
|
||||
Err(_) => {
|
||||
fail_versions += 1;
|
||||
}
|
||||
Err(_) => fail_versions += 1,
|
||||
}
|
||||
}
|
||||
custom.insert("success_versions", success_versions.to_string());
|
||||
@@ -901,14 +905,11 @@ impl FolderScanner {
|
||||
break;
|
||||
}
|
||||
if !was_compacted {
|
||||
self.new_cache.replace_hashed(&this_hash, &Some(folder.parent.clone()), &into);
|
||||
self.new_cache.replace_hashed(&this_hash, &Some(folder.parent.clone()), into);
|
||||
}
|
||||
|
||||
if !into.compacted && self.new_cache.info.name != folder.name {
|
||||
let mut flat = self
|
||||
.new_cache
|
||||
.size_recursive(&this_hash.key())
|
||||
.unwrap_or(DataUsageEntry::default());
|
||||
let mut flat = self.new_cache.size_recursive(&this_hash.key()).unwrap_or_default();
|
||||
flat.compacted = true;
|
||||
let compact = if flat.objects < DATA_SCANNER_COMPACT_LEAST_OBJECT.try_into().unwrap() {
|
||||
true
|
||||
@@ -1079,3 +1080,5 @@ pub async fn scan_data_folder(
|
||||
close_disk().await;
|
||||
Ok(s.new_cache)
|
||||
}
|
||||
|
||||
// pub fn eval_action_from_lifecycle(lc: &BucketLifecycleConfiguration, lr: &ObjectLockConfiguration, rcfg: &ReplicationConfiguration, obj: &ObjectInfo)
|
||||
|
||||
@@ -101,10 +101,11 @@ impl LockedLastMinuteLatency {
|
||||
{
|
||||
let old = self.cached.clone();
|
||||
self.cached = AccElem::default();
|
||||
let mut a = AccElem::default();
|
||||
a.size = old.size;
|
||||
a.total = old.total;
|
||||
a.n = old.n;
|
||||
let a = AccElem {
|
||||
size: old.size,
|
||||
total: old.total,
|
||||
n: old.n,
|
||||
};
|
||||
let _ = self.mu.write().await;
|
||||
self.latency.add_all(t - 1, &a);
|
||||
}
|
||||
@@ -130,6 +131,12 @@ pub struct ScannerMetrics {
|
||||
current_paths: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for ScannerMetrics {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScannerMetrics {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -122,7 +122,7 @@ pub async fn store_data_usage_in_backend(mut rx: Receiver<DataUsageInfo>) {
|
||||
Some(data_usage_info) => {
|
||||
if let Ok(data) = serde_json::to_vec(&data_usage_info) {
|
||||
if attempts > 10 {
|
||||
let _ = save_config(store, &format!("{}{}", DATA_USAGE_OBJ_NAME_PATH.to_string(), ".bkp"), &data).await;
|
||||
let _ = save_config(store, &format!("{}{}", *DATA_USAGE_OBJ_NAME_PATH, ".bkp"), &data).await;
|
||||
attempts += 1;
|
||||
}
|
||||
let _ = save_config(store, &DATA_USAGE_OBJ_NAME_PATH, &data).await;
|
||||
|
||||
@@ -17,7 +17,6 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::u64;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::time::sleep;
|
||||
|
||||
@@ -158,7 +157,7 @@ impl SizeHistogram {
|
||||
for (count, oh) in self.0.iter().zip(OBJECTS_HISTOGRAM_INTERVALS.iter()) {
|
||||
if ByteSize::kib(1).as_u64() == oh.start && oh.end == ByteSize::mib(1).as_u64() - 1 {
|
||||
res.insert(oh.name.to_string(), spl_count);
|
||||
} else if ByteSize::kib(1).as_u64() <= oh.start && oh.end <= ByteSize::mib(1).as_u64() - 1 {
|
||||
} else if ByteSize::kib(1).as_u64() <= oh.start && oh.end < ByteSize::mib(1).as_u64() {
|
||||
spl_count += count;
|
||||
res.insert(oh.name.to_string(), *count);
|
||||
} else {
|
||||
@@ -310,7 +309,7 @@ impl DataUsageEntry {
|
||||
s_rep.replica_size += o_rep.replica_size;
|
||||
s_rep.replica_count += o_rep.replica_count;
|
||||
for (arn, stat) in o_rep.targets.iter() {
|
||||
let st = s_rep.targets.entry(arn.clone()).or_insert(ReplicationStats::default());
|
||||
let st = s_rep.targets.entry(arn.clone()).or_default();
|
||||
*st = ReplicationStats {
|
||||
pending_size: stat.pending_size + st.pending_size,
|
||||
failed_size: stat.failed_size + st.failed_size,
|
||||
@@ -456,7 +455,7 @@ impl DataUsageCache {
|
||||
tokio::spawn(async move {
|
||||
let _ = save_config(&store_clone, &format!("{}{}", &name_clone, ".bkp"), &buf_clone).await;
|
||||
});
|
||||
save_config(&store, name, &buf).await
|
||||
save_config(store, name, &buf).await
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, path: &str, parent: &str, e: DataUsageEntry) {
|
||||
@@ -465,7 +464,7 @@ impl DataUsageCache {
|
||||
if !parent.is_empty() {
|
||||
let phash = hash_path(parent);
|
||||
let p = {
|
||||
let p = self.cache.entry(phash.key()).or_insert(DataUsageEntry::default());
|
||||
let p = self.cache.entry(phash.key()).or_default();
|
||||
p.add_child(&hash);
|
||||
p.clone()
|
||||
};
|
||||
@@ -476,10 +475,7 @@ impl DataUsageCache {
|
||||
pub fn replace_hashed(&mut self, hash: &DataUsageHash, parent: &Option<DataUsageHash>, e: &DataUsageEntry) {
|
||||
self.cache.insert(hash.key(), e.clone());
|
||||
if let Some(parent) = parent {
|
||||
self.cache
|
||||
.entry(parent.key())
|
||||
.or_insert(DataUsageEntry::default())
|
||||
.add_child(hash);
|
||||
self.cache.entry(parent.key()).or_default().add_child(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,11 +484,7 @@ impl DataUsageCache {
|
||||
}
|
||||
|
||||
pub fn find_children_copy(&mut self, h: DataUsageHash) -> DataUsageHashMap {
|
||||
self.cache
|
||||
.entry(h.string())
|
||||
.or_insert(DataUsageEntry::default())
|
||||
.children
|
||||
.clone()
|
||||
self.cache.entry(h.string()).or_default().children.clone()
|
||||
}
|
||||
|
||||
pub fn flatten(&self, root: &DataUsageEntry) -> DataUsageEntry {
|
||||
@@ -511,21 +503,18 @@ impl DataUsageCache {
|
||||
}
|
||||
|
||||
pub fn copy_with_children(&mut self, src: &DataUsageCache, hash: &DataUsageHash, parent: &Option<DataUsageHash>) {
|
||||
match src.cache.get(&hash.string()) {
|
||||
Some(e) => {
|
||||
self.cache.insert(hash.key(), e.clone());
|
||||
for ch in e.children.iter() {
|
||||
if *ch == hash.key() {
|
||||
return;
|
||||
}
|
||||
self.copy_with_children(src, &DataUsageHash(ch.to_string()), &Some(hash.clone()));
|
||||
}
|
||||
if let Some(parent) = parent {
|
||||
let p = self.cache.entry(parent.key()).or_insert(DataUsageEntry::default());
|
||||
p.add_child(hash);
|
||||
if let Some(e) = src.cache.get(&hash.string()) {
|
||||
self.cache.insert(hash.key(), e.clone());
|
||||
for ch in e.children.iter() {
|
||||
if *ch == hash.key() {
|
||||
return;
|
||||
}
|
||||
self.copy_with_children(src, &DataUsageHash(ch.to_string()), &Some(hash.clone()));
|
||||
}
|
||||
if let Some(parent) = parent {
|
||||
let p = self.cache.entry(parent.key()).or_default();
|
||||
p.add_child(hash);
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +541,7 @@ impl DataUsageCache {
|
||||
if flat.replication_stats.is_some() && flat.replication_stats.as_ref().unwrap().empty() {
|
||||
flat.replication_stats = None;
|
||||
}
|
||||
return Some(flat);
|
||||
Some(flat)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
@@ -623,7 +612,7 @@ impl DataUsageCache {
|
||||
}
|
||||
|
||||
if e.children.len() > limit && compact_self {
|
||||
let mut flat = self.size_recursive(&path.key()).unwrap_or(DataUsageEntry::default());
|
||||
let mut flat = self.size_recursive(&path.key()).unwrap_or_default();
|
||||
flat.compacted = true;
|
||||
self.delete_recursive(path);
|
||||
self.replace_hashed(path, &None, &flat);
|
||||
@@ -639,7 +628,7 @@ impl DataUsageCache {
|
||||
add(self, path, &mut leaves);
|
||||
leaves.sort_by(|a, b| a.objects.cmp(&b.objects));
|
||||
|
||||
while remove > 0 && leaves.len() > 0 {
|
||||
while remove > 0 && !leaves.is_empty() {
|
||||
let e = leaves.first().unwrap();
|
||||
let candidate = e.path.clone();
|
||||
if candidate == *path && !compact_self {
|
||||
@@ -676,7 +665,7 @@ impl DataUsageCache {
|
||||
|
||||
let mut n = root.children.len();
|
||||
for ch in root.children.iter() {
|
||||
n += self.total_children_rec(&ch);
|
||||
n += self.total_children_rec(ch);
|
||||
}
|
||||
n
|
||||
}
|
||||
@@ -724,7 +713,7 @@ impl DataUsageCache {
|
||||
None => return DataUsageInfo::default(),
|
||||
};
|
||||
let flat = self.flatten(&e);
|
||||
let dui = DataUsageInfo {
|
||||
DataUsageInfo {
|
||||
last_update: self.info.last_update,
|
||||
objects_total_count: flat.objects as u64,
|
||||
versions_total_count: flat.versions as u64,
|
||||
@@ -733,8 +722,7 @@ impl DataUsageCache {
|
||||
buckets_count: e.children.len() as u64,
|
||||
buckets_usage: self.buckets_usage_info(buckets),
|
||||
..Default::default()
|
||||
};
|
||||
dui
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buckets_usage_info(&self, buckets: &[BucketInfo]) -> HashMap<String, BucketUsageInfo> {
|
||||
@@ -807,9 +795,7 @@ fn add(data_usage_cache: &DataUsageCache, path: &DataUsageHash, leaves: &mut Vec
|
||||
return;
|
||||
}
|
||||
|
||||
let sz = data_usage_cache
|
||||
.size_recursive(&path.key())
|
||||
.unwrap_or(DataUsageEntry::default());
|
||||
let sz = data_usage_cache.size_recursive(&path.key()).unwrap_or_default();
|
||||
leaves.push(Inner {
|
||||
objects: sz.objects,
|
||||
path: path.clone(),
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
pub const ERR_IGNORE_FILE_CONTRIB: &str = "ignore this file's contribution toward data-usage";
|
||||
pub const ERR_SKIP_FILE: &str = "skip this file";
|
||||
pub const ERR_HEAL_STOP_SIGNALLED: &str = "heal stop signaled";
|
||||
pub const ERR_HEAL_IDLE_TIMEOUT: &str = "healing results were not consumed for too long";
|
||||
pub const ERR_RETRY_HEALING: &str = "some items failed to heal, we will retry healing this drive again";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use std::{path::Path, time::SystemTime};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
@@ -38,7 +37,11 @@ pub const DRIVE_STATE_ROOT_MOUNT: &str = "root-mount";
|
||||
pub const DRIVE_STATE_UNKNOWN: &str = "unknown";
|
||||
pub const DRIVE_STATE_UNFORMATTED: &str = "unformatted"; // only returned by disk
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
lazy_static! {
|
||||
pub static ref TIME_SENTINEL: OffsetDateTime = OffsetDateTime::from_unix_timestamp(0).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct HealOpts {
|
||||
pub recursive: bool,
|
||||
pub dry_run: bool,
|
||||
@@ -102,8 +105,8 @@ pub struct HealingDisk {
|
||||
pub disk_index: Option<usize>,
|
||||
pub endpoint: String,
|
||||
pub path: String,
|
||||
pub started: u64,
|
||||
pub last_update: u64,
|
||||
pub started: Option<OffsetDateTime>,
|
||||
pub last_update: Option<SystemTime>,
|
||||
pub retry_attempts: u64,
|
||||
pub objects_total_count: u64,
|
||||
pub objects_total_size: u64,
|
||||
@@ -132,8 +135,8 @@ pub struct HealingTracker {
|
||||
pub disk_index: Option<usize>,
|
||||
pub path: String,
|
||||
pub endpoint: String,
|
||||
pub started: u64,
|
||||
pub last_update: u64,
|
||||
pub started: Option<OffsetDateTime>,
|
||||
pub last_update: Option<SystemTime>,
|
||||
pub objects_total_count: u64,
|
||||
pub objects_total_size: u64,
|
||||
pub items_healed: u64,
|
||||
@@ -161,8 +164,7 @@ pub struct HealingTracker {
|
||||
|
||||
impl HealingTracker {
|
||||
pub fn marshal_msg(&self) -> Result<Vec<u8>> {
|
||||
serde_json::to_vec(self)
|
||||
.map_err(|err| Error::from_string(err.to_string()))
|
||||
serde_json::to_vec(self).map_err(|err| Error::from_string(err.to_string()))
|
||||
}
|
||||
|
||||
pub fn unmarshal_msg(data: &[u8]) -> Result<Self> {
|
||||
@@ -187,7 +189,7 @@ impl HealingTracker {
|
||||
self.object = String::new();
|
||||
}
|
||||
|
||||
pub async fn get_last_update(&self) -> u64 {
|
||||
pub async fn get_last_update(&self) -> Option<SystemTime> {
|
||||
let _ = self.mu.read().await;
|
||||
|
||||
self.last_update
|
||||
@@ -234,7 +236,7 @@ impl HealingTracker {
|
||||
|
||||
pub async fn update(&mut self) -> Result<()> {
|
||||
if let Some(disk) = &self.disk {
|
||||
if healing(&disk.path().to_string_lossy().to_string()).await?.is_none() {
|
||||
if healing(disk.path().to_string_lossy().as_ref()).await?.is_none() {
|
||||
return Err(Error::from_string(format!("healingTracker: drive {} is not marked as healing", self.id)));
|
||||
}
|
||||
let _ = self.mu.write().await;
|
||||
@@ -262,14 +264,11 @@ impl HealingTracker {
|
||||
(self.pool_index, self.set_index, self.disk_index) = store.get_pool_and_set(&self.id).await?;
|
||||
}
|
||||
|
||||
self.last_update = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
.as_secs();
|
||||
self.last_update = Some(SystemTime::now());
|
||||
|
||||
let htracker_bytes = self.marshal_msg()?;
|
||||
|
||||
GLOBAL_BackgroundHealState.write().await.update_heal_status(&self).await;
|
||||
GLOBAL_BackgroundHealState.write().await.update_heal_status(self).await;
|
||||
|
||||
if let Some(disk) = &self.disk {
|
||||
let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME);
|
||||
@@ -280,7 +279,7 @@ impl HealingTracker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete(&self) -> Result<()> {
|
||||
pub async fn delete(&self) -> Result<()> {
|
||||
if let Some(disk) = &self.disk {
|
||||
let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME);
|
||||
return disk
|
||||
@@ -299,7 +298,7 @@ impl HealingTracker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_healed(&self, bucket: &str) -> bool {
|
||||
pub async fn is_healed(&self, bucket: &str) -> bool {
|
||||
let _ = self.mu.read().await;
|
||||
for v in self.healed_buckets.iter() {
|
||||
if v == bucket {
|
||||
@@ -310,7 +309,7 @@ impl HealingTracker {
|
||||
false
|
||||
}
|
||||
|
||||
async fn resume(&mut self) {
|
||||
pub async fn resume(&mut self) {
|
||||
let _ = self.mu.write().await;
|
||||
|
||||
self.items_healed = self.resume_items_healed;
|
||||
@@ -321,7 +320,7 @@ impl HealingTracker {
|
||||
self.bytes_skipped = self.resume_bytes_skipped;
|
||||
}
|
||||
|
||||
async fn bucket_done(&mut self, bucket: &str) {
|
||||
pub async fn bucket_done(&mut self, bucket: &str) {
|
||||
let _ = self.mu.write().await;
|
||||
|
||||
self.resume_items_healed = self.items_healed;
|
||||
@@ -383,34 +382,34 @@ impl Clone for HealingTracker {
|
||||
Self {
|
||||
disk: self.disk.clone(),
|
||||
id: self.id.clone(),
|
||||
pool_index: self.pool_index.clone(),
|
||||
set_index: self.set_index.clone(),
|
||||
disk_index: self.disk_index.clone(),
|
||||
pool_index: self.pool_index,
|
||||
set_index: self.set_index,
|
||||
disk_index: self.disk_index,
|
||||
path: self.path.clone(),
|
||||
endpoint: self.endpoint.clone(),
|
||||
started: self.started.clone(),
|
||||
last_update: self.last_update.clone(),
|
||||
objects_total_count: self.objects_total_count.clone(),
|
||||
objects_total_size: self.objects_total_size.clone(),
|
||||
items_healed: self.items_healed.clone(),
|
||||
items_failed: self.items_failed.clone(),
|
||||
item_skipped: self.item_skipped.clone(),
|
||||
bytes_done: self.bytes_done.clone(),
|
||||
bytes_failed: self.bytes_failed.clone(),
|
||||
bytes_skipped: self.bytes_skipped.clone(),
|
||||
started: self.started,
|
||||
last_update: self.last_update,
|
||||
objects_total_count: self.objects_total_count,
|
||||
objects_total_size: self.objects_total_size,
|
||||
items_healed: self.items_healed,
|
||||
items_failed: self.items_failed,
|
||||
item_skipped: self.item_skipped,
|
||||
bytes_done: self.bytes_done,
|
||||
bytes_failed: self.bytes_failed,
|
||||
bytes_skipped: self.bytes_skipped,
|
||||
bucket: self.bucket.clone(),
|
||||
object: self.object.clone(),
|
||||
resume_items_healed: self.resume_items_healed.clone(),
|
||||
resume_items_failed: self.resume_items_failed.clone(),
|
||||
resume_items_skipped: self.resume_items_skipped.clone(),
|
||||
resume_bytes_done: self.resume_bytes_done.clone(),
|
||||
resume_bytes_failed: self.resume_bytes_failed.clone(),
|
||||
resume_bytes_skipped: self.resume_bytes_skipped.clone(),
|
||||
resume_items_healed: self.resume_items_healed,
|
||||
resume_items_failed: self.resume_items_failed,
|
||||
resume_items_skipped: self.resume_items_skipped,
|
||||
resume_bytes_done: self.resume_bytes_done,
|
||||
resume_bytes_failed: self.resume_bytes_failed,
|
||||
resume_bytes_skipped: self.resume_bytes_skipped,
|
||||
queue_buckets: self.queue_buckets.clone(),
|
||||
healed_buckets: self.healed_buckets.clone(),
|
||||
heal_id: self.heal_id.clone(),
|
||||
retry_attempts: self.retry_attempts.clone(),
|
||||
finished: self.finished.clone(),
|
||||
retry_attempts: self.retry_attempts,
|
||||
finished: self.finished,
|
||||
mu: RwLock::new(false),
|
||||
}
|
||||
}
|
||||
@@ -441,22 +440,19 @@ pub async fn load_healing_tracker(disk: &Option<DiskStore>) -> Result<HealingTra
|
||||
}
|
||||
|
||||
pub async fn init_healing_tracker(disk: DiskStore, heal_id: &str) -> Result<HealingTracker> {
|
||||
let mut healing_tracker = HealingTracker::default();
|
||||
healing_tracker.id = disk.get_disk_id().await?.map_or("".to_string(), |id| id.to_string());
|
||||
healing_tracker.heal_id = heal_id.to_string();
|
||||
healing_tracker.path = disk.to_string();
|
||||
healing_tracker.endpoint = disk.endpoint().to_string();
|
||||
healing_tracker.started = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
.as_secs();
|
||||
let disk_location = disk.get_disk_location();
|
||||
healing_tracker.pool_index = disk_location.pool_idx;
|
||||
healing_tracker.set_index = disk_location.set_idx;
|
||||
healing_tracker.disk_index = disk_location.disk_idx;
|
||||
healing_tracker.disk = Some(disk);
|
||||
|
||||
Ok(healing_tracker)
|
||||
Ok(HealingTracker {
|
||||
id: disk.get_disk_id().await?.map_or("".to_string(), |id| id.to_string()),
|
||||
heal_id: heal_id.to_string(),
|
||||
path: disk.to_string(),
|
||||
endpoint: disk.endpoint().to_string(),
|
||||
started: Some(OffsetDateTime::now_utc()),
|
||||
pool_index: disk_location.pool_idx,
|
||||
set_index: disk_location.set_idx,
|
||||
disk_index: disk_location.disk_idx,
|
||||
disk: Some(disk),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn healing(derive_path: &str) -> Result<Option<HealingTracker>> {
|
||||
|
||||
+234
-60
@@ -1,12 +1,34 @@
|
||||
use super::{
|
||||
background_heal_ops::HealTask,
|
||||
data_scanner::HEAL_DELETE_DANGLING,
|
||||
error::ERR_SKIP_FILE,
|
||||
heal_commands::{
|
||||
HealItemType, HealOpts, HealResultItem, HealScanMode, HealStopSuccess, HealingDisk, HealingTracker,
|
||||
HEAL_ITEM_BUCKET_METADATA,
|
||||
},
|
||||
};
|
||||
use crate::heal::heal_commands::{HEAL_ITEM_BUCKET, HEAL_ITEM_OBJECT};
|
||||
use crate::store_api::StorageAPI;
|
||||
use crate::{
|
||||
config::common::CONFIG_PREFIX,
|
||||
disk::RUSTFS_META_BUCKET,
|
||||
global::GLOBAL_BackgroundHealRoutine,
|
||||
heal::{
|
||||
error::ERR_HEAL_STOP_SIGNALLED,
|
||||
heal_commands::{HealDriveInfo, DRIVE_STATE_OK},
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
disk::{endpoint::Endpoint, MetaCacheEntry},
|
||||
endpoints::Endpoints,
|
||||
error::{Error, Result},
|
||||
global::GLOBAL_IsDistErasure,
|
||||
heal::heal_commands::{HealStartSuccess, HEAL_UNKNOWN_SCAN},
|
||||
new_object_layer_fn,
|
||||
utils::path::has_profix,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use s3s::{S3Error, S3ErrorCode};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
@@ -24,19 +46,13 @@ use tokio::{
|
||||
},
|
||||
time::{interval, sleep},
|
||||
};
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{
|
||||
background_heal_ops::HealTask,
|
||||
data_scanner::HEAL_DELETE_DANGLING,
|
||||
heal_commands::{HealItemType, HealOpts, HealResultItem, HealScanMode, HealStopSuccess, HealingDisk, HealingTracker},
|
||||
};
|
||||
|
||||
type HealStatusSummary = String;
|
||||
type ItemsMap = HashMap<HealItemType, usize>;
|
||||
pub type HealObjectFn = Arc<dyn Fn(&str, &str, &str, HealScanMode) -> Result<()> + Send + Sync>;
|
||||
pub type HealEntryFn =
|
||||
Box<dyn Fn(String, MetaCacheEntry, HealScanMode) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send>;
|
||||
Arc<dyn Fn(String, MetaCacheEntry, HealScanMode) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync + 'static>;
|
||||
|
||||
pub const BG_HEALING_UUID: &str = "0000-0000-0000-0000";
|
||||
pub const HEALING_TRACKER_FILENAME: &str = ".healing.bin";
|
||||
@@ -174,19 +190,19 @@ impl HealSequence {
|
||||
}
|
||||
|
||||
impl HealSequence {
|
||||
fn get_scanned_items_count(&self) -> usize {
|
||||
fn _get_scanned_items_count(&self) -> usize {
|
||||
self.scanned_items_map.values().sum()
|
||||
}
|
||||
|
||||
fn get_scanned_items_map(&self) -> ItemsMap {
|
||||
fn _get_scanned_items_map(&self) -> ItemsMap {
|
||||
self.scanned_items_map.clone()
|
||||
}
|
||||
|
||||
fn get_healed_items_map(&self) -> ItemsMap {
|
||||
fn _get_healed_items_map(&self) -> ItemsMap {
|
||||
self.healed_items_map.clone()
|
||||
}
|
||||
|
||||
fn get_heal_failed_items_map(&self) -> ItemsMap {
|
||||
fn _get_heal_failed_items_map(&self) -> ItemsMap {
|
||||
self.heal_failed_items_map.clone()
|
||||
}
|
||||
|
||||
@@ -223,11 +239,11 @@ impl HealSequence {
|
||||
}
|
||||
|
||||
async fn has_ended(&self) -> bool {
|
||||
if self.client_token == BG_HEALING_UUID.to_string() {
|
||||
if self.client_token == *BG_HEALING_UUID {
|
||||
return false;
|
||||
}
|
||||
|
||||
!(*(self.end_time.read().await) == self.start_time)
|
||||
*(self.end_time.read().await) != self.start_time
|
||||
}
|
||||
|
||||
async fn stop(&self) {
|
||||
@@ -238,6 +254,7 @@ impl HealSequence {
|
||||
async fn push_heal_result_item(&self, r: &HealResultItem) -> Result<()> {
|
||||
let mut r = r.clone();
|
||||
let mut interval_timer = interval(HEAL_UNCONSUMED_TIMEOUT);
|
||||
#[allow(unused_assignments)]
|
||||
let mut items_len = 0;
|
||||
loop {
|
||||
{
|
||||
@@ -282,31 +299,108 @@ impl HealSequence {
|
||||
task.opts.scan_mode = HEAL_UNKNOWN_SCAN;
|
||||
}
|
||||
|
||||
self.count_scanned(heal_type);
|
||||
self.count_scanned(heal_type.clone());
|
||||
|
||||
if source.no_wait {}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn heal_disk_meta() -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn heal_items(&self, buckets_only: bool) -> Result<()> {
|
||||
if self.client_token == BG_HEALING_UUID.to_string() {
|
||||
if source.no_wait {
|
||||
let task_str = format!("{:?}", task);
|
||||
if GLOBAL_BackgroundHealRoutine.read().await.tasks_tx.try_send(task).is_ok() {
|
||||
info!("Task in the queue: {:?}", task_str);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
todo!()
|
||||
let (resp_tx, mut resp_rx) = mpsc::channel(1);
|
||||
task.resp_tx = Some(resp_tx);
|
||||
|
||||
let task_str = format!("{:?}", task);
|
||||
if GLOBAL_BackgroundHealRoutine.read().await.tasks_tx.try_send(task).is_ok() {
|
||||
info!("Task in the queue: {:?}", task_str);
|
||||
}
|
||||
let count_ok_drives = |drivers: &[HealDriveInfo]| {
|
||||
let mut count = 0;
|
||||
for drive in drivers.iter() {
|
||||
if drive.state == DRIVE_STATE_OK {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
count
|
||||
};
|
||||
|
||||
loop {
|
||||
match resp_rx.recv().await {
|
||||
Some(mut res) => {
|
||||
if res.err.is_none() {
|
||||
self.count_healed(heal_type.clone());
|
||||
} else {
|
||||
self.count_failed(heal_type.clone());
|
||||
}
|
||||
if !self.report_progress {
|
||||
if let Some(err) = res.err {
|
||||
if err.to_string() == ERR_SKIP_FILE {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
res.result.heal_item_type = heal_type.clone();
|
||||
if let Some(err) = res.err.as_ref() {
|
||||
res.result.detail = err.to_string();
|
||||
}
|
||||
if res.result.parity_blocks > 0
|
||||
&& res.result.data_blocks > 0
|
||||
&& res.result.data_blocks > res.result.parity_blocks
|
||||
{
|
||||
let got = count_ok_drives(&res.result.after);
|
||||
if got < res.result.parity_blocks {
|
||||
res.result.detail = format!(
|
||||
"quorum loss - expected {} minimum, got drive states in OK {}",
|
||||
res.result.parity_blocks, got
|
||||
);
|
||||
}
|
||||
}
|
||||
return self.push_heal_result_item(&res.result).await;
|
||||
}
|
||||
None => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn traverse_and_heal(&self) {
|
||||
async fn heal_disk_meta(h: Arc<RwLock<HealSequence>>) -> Result<()> {
|
||||
HealSequence::heal_rustfs_sys_meta(h, CONFIG_PREFIX).await
|
||||
}
|
||||
|
||||
async fn heal_items(h: Arc<RwLock<HealSequence>>, buckets_only: bool) -> Result<()> {
|
||||
if h.read().await.client_token == *BG_HEALING_UUID {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Self::heal_disk_meta(h.clone()).await?;
|
||||
let bucket = h.read().await.bucket.clone();
|
||||
Self::heal_bucket(h.clone(), &bucket, buckets_only).await
|
||||
}
|
||||
|
||||
async fn traverse_and_heal(h: Arc<RwLock<HealSequence>>) {
|
||||
let buckets_only = false;
|
||||
let result = match Self::heal_items(h.clone(), buckets_only).await {
|
||||
Ok(_) => None,
|
||||
Err(err) => Some(err),
|
||||
};
|
||||
let _ = h.read().await.traverse_and_heal_done_tx.read().await.send(result).await;
|
||||
}
|
||||
|
||||
fn heal_rustfs_sys_meta(&self, meta_prefix: String) -> Result<()> {
|
||||
todo!()
|
||||
async fn heal_rustfs_sys_meta(h: Arc<RwLock<HealSequence>>, meta_prefix: &str) -> Result<()> {
|
||||
let layer = new_object_layer_fn();
|
||||
let lock = layer.read().await;
|
||||
let store = match lock.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::from(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()))),
|
||||
};
|
||||
let setting = h.read().await.setting;
|
||||
store
|
||||
.heal_objects(RUSTFS_META_BUCKET, meta_prefix, &setting, h.clone(), true)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn is_done(&self) -> bool {
|
||||
@@ -316,14 +410,101 @@ impl HealSequence {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn heal_bucket(hs: Arc<RwLock<HealSequence>>, bucket: &str, bucket_only: bool) -> Result<()> {
|
||||
let (object, setting) = {
|
||||
let mut hs_w = hs.write().await;
|
||||
hs_w.queue_heal_task(
|
||||
HealSource {
|
||||
bucket: bucket.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
HEAL_ITEM_BUCKET.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if bucket_only {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !hs_w.setting.recursive {
|
||||
if !hs_w.object.is_empty() {
|
||||
HealSequence::heal_object(hs.clone(), bucket, &hs_w.object, "", hs_w.setting.scan_mode).await?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
(hs_w.object.clone(), hs_w.setting)
|
||||
};
|
||||
let layer = new_object_layer_fn();
|
||||
let lock = layer.read().await;
|
||||
let store = match lock.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return Err(Error::from(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()))),
|
||||
};
|
||||
store.heal_objects(bucket, &object, &setting, hs.clone(), false).await
|
||||
}
|
||||
|
||||
pub async fn heal_object(
|
||||
hs: Arc<RwLock<HealSequence>>,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
version_id: &str,
|
||||
_scan_mode: HealScanMode,
|
||||
) -> Result<()> {
|
||||
let mut hs_w = hs.write().await;
|
||||
if hs_w.is_quitting().await {
|
||||
return Err(Error::from_string(ERR_HEAL_STOP_SIGNALLED));
|
||||
}
|
||||
|
||||
let setting = hs_w.setting;
|
||||
hs_w.queue_heal_task(
|
||||
HealSource {
|
||||
bucket: bucket.to_string(),
|
||||
object: object.to_string(),
|
||||
version_id: version_id.to_string(),
|
||||
opts: Some(setting),
|
||||
..Default::default()
|
||||
},
|
||||
HEAL_ITEM_OBJECT.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn heal_meta_object(
|
||||
hs: Arc<RwLock<HealSequence>>,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
version_id: &str,
|
||||
_scan_mode: HealScanMode,
|
||||
) -> Result<()> {
|
||||
let mut hs_w = hs.write().await;
|
||||
if hs_w.is_quitting().await {
|
||||
return Err(Error::from_string(ERR_HEAL_STOP_SIGNALLED));
|
||||
}
|
||||
|
||||
hs_w.queue_heal_task(
|
||||
HealSource {
|
||||
bucket: bucket.to_string(),
|
||||
object: object.to_string(),
|
||||
version_id: version_id.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
HEAL_ITEM_BUCKET_METADATA.to_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn heal_sequence_start(h: Arc<RwLock<HealSequence>>) {
|
||||
let r = h.read().await;
|
||||
{
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(*current_status_w).summary = HEAL_RUNNING_STATUS.to_string();
|
||||
(*current_status_w).start_time = SystemTime::now()
|
||||
current_status_w.summary = HEAL_RUNNING_STATUS.to_string();
|
||||
current_status_w.start_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
.as_secs();
|
||||
@@ -331,7 +512,7 @@ pub async fn heal_sequence_start(h: Arc<RwLock<HealSequence>>) {
|
||||
|
||||
let h_clone = h.clone();
|
||||
spawn(async move {
|
||||
h_clone.read().await.traverse_and_heal().await;
|
||||
HealSequence::traverse_and_heal(h_clone).await;
|
||||
});
|
||||
|
||||
let h_clone_1 = h.clone();
|
||||
@@ -339,32 +520,27 @@ pub async fn heal_sequence_start(h: Arc<RwLock<HealSequence>>) {
|
||||
select! {
|
||||
_ = r.is_done() => {
|
||||
*(r.end_time.write().await) = SystemTime::now();
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(*current_status_w).summary = HEAL_FINISHED_STATUS.to_string();
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
current_status_w.summary = HEAL_FINISHED_STATUS.to_string();
|
||||
|
||||
spawn(async move {
|
||||
let binding = h_clone_1.read().await;
|
||||
let mut rx_w = binding.traverse_and_heal_done_rx.write().await;
|
||||
rx_w.recv().await;
|
||||
spawn(async move {
|
||||
let binding = h_clone_1.read().await;
|
||||
let mut rx_w = binding.traverse_and_heal_done_rx.write().await;
|
||||
rx_w.recv().await;
|
||||
});
|
||||
}
|
||||
result = x.recv() => {
|
||||
match result {
|
||||
Some(err) => {
|
||||
match err {
|
||||
Some(err) => {
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(current_status_w).summary = HEAL_STOPPED_STATUS.to_string();
|
||||
(current_status_w).failure_detail = err.to_string();
|
||||
},
|
||||
None => {
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(current_status_w).summary = HEAL_FINISHED_STATUS.to_string();
|
||||
}
|
||||
if let Some(err) = result {
|
||||
match err {
|
||||
Some(err) => {
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(current_status_w).summary = HEAL_STOPPED_STATUS.to_string();
|
||||
(current_status_w).failure_detail = err.to_string();
|
||||
},
|
||||
None => {
|
||||
let mut current_status_w = r.current_status.write().await;
|
||||
(current_status_w).summary = HEAL_FINISHED_STATUS.to_string();
|
||||
}
|
||||
},
|
||||
None => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,18 +731,16 @@ impl AllHealState {
|
||||
let path_s = path.to_str().unwrap();
|
||||
if r.force_started {
|
||||
self.stop_heal_sequence(path_s).await?;
|
||||
} else {
|
||||
if let Some(hs) = self.get_heal_sequence(path_s).await {
|
||||
if !hs.read().await.has_ended().await {
|
||||
return Err(Error::from_string(format!("Heal is already running on the given path (use force-start option to stop and start afresh). The heal was started by IP {} at {:?}, token is {}", r.client_address, r.start_time, r.client_token)));
|
||||
}
|
||||
} else if let Some(hs) = self.get_heal_sequence(path_s).await {
|
||||
if !hs.read().await.has_ended().await {
|
||||
return Err(Error::from_string(format!("Heal is already running on the given path (use force-start option to stop and start afresh). The heal was started by IP {} at {:?}, token is {}", r.client_address, r.start_time, r.client_token)));
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.mu.write().await;
|
||||
|
||||
for (k, v) in self.heal_seq_map.iter() {
|
||||
if !v.read().await.has_ended().await && (has_profix(&k, path_s) || has_profix(path_s, &k)) {
|
||||
if !v.read().await.has_ended().await && (has_profix(k, path_s) || has_profix(path_s, k)) {
|
||||
return Err(Error::from_string(format!(
|
||||
"The provided heal sequence path overlaps with an existing heal path: {}",
|
||||
k
|
||||
|
||||
Reference in New Issue
Block a user