mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-12 16:16:55 +00:00
a63b79004c
* fix(scanner): make distributed usage cycles authoritative * fix(scanner): close distributed refresh races * fix(config): align scanner reload integration * fix(admin): scope config test helpers * fix(scanner): harden distributed usage convergence * fix(scanner): preserve rolling activity compatibility * fix(admin): expose non-secret optional config values * fix(scanner): acknowledge distributed dirty usage * fix(ecstore): make bucket mutations cancellation safe * fix(scanner): preserve pending dirty acknowledgements * test(obs): account for superseded scanner metric * fix(api): reject excess detached bucket mutations * test: close scanner convergence coverage gaps * fix(scanner): make path tracking cleanup one-shot --------- Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com> Co-authored-by: houseme <housemecn@gmail.com>
241 lines
8.2 KiB
Rust
241 lines
8.2 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.
|
|
|
|
//! `BucketOperations` for `SetDisks`.
|
|
//!
|
|
//! P4 of the SetDisks God-Object split (tracking backlog#815, issue #819).
|
|
//! Relocated verbatim from `set_disk/mod.rs`; the contract stays implemented
|
|
//! `for SetDisks`, so its associated-type bounds are unchanged and runtime
|
|
//! behavior is the same.
|
|
|
|
use super::super::*;
|
|
|
|
impl SetDisks {
|
|
pub(crate) async fn list_bucket_for_scanner(&self, _opts: &BucketOptions) -> Result<(Vec<BucketInfo>, bool)> {
|
|
let disks = self.disk_inventory().await;
|
|
let write_quorum = (disks.len() / 2) + 1;
|
|
|
|
let mut futures = Vec::with_capacity(disks.len());
|
|
for disk in disks {
|
|
futures.push(async move {
|
|
match disk {
|
|
Some(disk) => disk.list_volumes().await,
|
|
None => Err(DiskError::DiskNotFound),
|
|
}
|
|
});
|
|
}
|
|
|
|
let results = join_all(futures).await;
|
|
let mut topology_complete = results.iter().all(|result| result.is_ok());
|
|
let mut infos = Vec::with_capacity(results.len());
|
|
let mut errs = Vec::with_capacity(results.len());
|
|
for result in results {
|
|
match result {
|
|
Ok(volumes) => {
|
|
infos.push(Some(volumes));
|
|
errs.push(None);
|
|
}
|
|
Err(err) => {
|
|
infos.push(None);
|
|
errs.push(Some(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(err) = reduce_write_quorum_errs(&errs, BUCKET_OP_IGNORED_ERRS, write_quorum) {
|
|
return Err(err.into());
|
|
}
|
|
|
|
let mut counts: HashMap<String, (usize, BucketInfo)> = HashMap::new();
|
|
for volumes in infos.into_iter().flatten() {
|
|
for volume in volumes {
|
|
if is_reserved_or_invalid_bucket(&volume.name, false) {
|
|
continue;
|
|
}
|
|
|
|
let entry = counts.entry(volume.name.clone()).or_insert((
|
|
0,
|
|
BucketInfo {
|
|
name: volume.name.clone(),
|
|
created: volume.created,
|
|
..Default::default()
|
|
},
|
|
));
|
|
entry.0 += 1;
|
|
}
|
|
}
|
|
|
|
topology_complete &= counts.values().all(|(count, _)| *count >= write_quorum);
|
|
let mut buckets = counts
|
|
.into_values()
|
|
.filter_map(|(count, bucket)| (count >= write_quorum).then_some(bucket))
|
|
.collect::<Vec<_>>();
|
|
buckets.sort_by(|left, right| left.name.cmp(&right.name));
|
|
Ok((buckets, topology_complete))
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl BucketOperations for SetDisks {
|
|
type Error = Error;
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
async fn make_bucket(&self, bucket: &str, opts: &MakeBucketOptions) -> Result<()> {
|
|
let disks = self.disk_inventory().await;
|
|
let write_quorum = (disks.len() / 2) + 1;
|
|
let force_create = opts.force_create;
|
|
|
|
let mut futures = Vec::with_capacity(disks.len());
|
|
for disk in disks {
|
|
let bucket = bucket.to_string();
|
|
futures.push(async move {
|
|
match disk {
|
|
Some(disk) => match disk.make_volume(&bucket).await {
|
|
Ok(()) => Ok(()),
|
|
Err(err) if force_create && matches!(err, DiskError::VolumeExists) => Ok(()),
|
|
Err(err) => Err(err),
|
|
},
|
|
None => Err(DiskError::DiskNotFound),
|
|
}
|
|
});
|
|
}
|
|
|
|
let results = join_all(futures).await;
|
|
let errs = results
|
|
.into_iter()
|
|
.map(|result| result.err())
|
|
.collect::<Vec<Option<DiskError>>>();
|
|
|
|
if let Some(err) = reduce_write_quorum_errs(&errs, BUCKET_OP_IGNORED_ERRS, write_quorum) {
|
|
return Err(err.into());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
async fn get_bucket_info(&self, bucket: &str, _opts: &BucketOptions) -> Result<BucketInfo> {
|
|
let disks = self.disk_inventory().await;
|
|
let write_quorum = (disks.len() / 2) + 1;
|
|
|
|
let mut futures = Vec::with_capacity(disks.len());
|
|
for disk in disks {
|
|
let bucket = bucket.to_string();
|
|
futures.push(async move {
|
|
match disk {
|
|
Some(disk) => disk.stat_volume(&bucket).await,
|
|
None => Err(DiskError::DiskNotFound),
|
|
}
|
|
});
|
|
}
|
|
|
|
let results = join_all(futures).await;
|
|
let mut infos = Vec::with_capacity(results.len());
|
|
let mut errs = Vec::with_capacity(results.len());
|
|
for result in results {
|
|
match result {
|
|
Ok(info) => {
|
|
infos.push(Some(info));
|
|
errs.push(None);
|
|
}
|
|
Err(err) => {
|
|
infos.push(None);
|
|
errs.push(Some(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(err) = reduce_write_quorum_errs(&errs, BUCKET_OP_IGNORED_ERRS, write_quorum) {
|
|
return Err(err.into());
|
|
}
|
|
|
|
let mut versioning = false;
|
|
let mut object_locking = false;
|
|
if let Ok(sys) = metadata_sys::get(bucket).await {
|
|
versioning = sys.versioning();
|
|
object_locking = sys.object_locking();
|
|
}
|
|
|
|
infos
|
|
.into_iter()
|
|
.flatten()
|
|
.next()
|
|
.map(|info| BucketInfo {
|
|
name: info.name,
|
|
created: info.created,
|
|
versioning,
|
|
object_locking,
|
|
..Default::default()
|
|
})
|
|
.ok_or(Error::VolumeNotFound)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
async fn list_bucket(&self, opts: &BucketOptions) -> Result<Vec<BucketInfo>> {
|
|
Ok(self.list_bucket_for_scanner(opts).await?.0)
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
async fn delete_bucket(&self, bucket: &str, opts: &DeleteBucketOptions) -> Result<()> {
|
|
let disks = self.disk_inventory().await;
|
|
let write_quorum = (disks.len() / 2) + 1;
|
|
|
|
let mut futures = Vec::with_capacity(disks.len());
|
|
for disk in disks.iter().cloned() {
|
|
let bucket = bucket.to_string();
|
|
let force = opts.force;
|
|
futures.push(async move {
|
|
match disk {
|
|
// Non-force refuses a non-empty bucket (VolumeNotEmpty); only
|
|
// an explicit force delete removes recursively (backlog#799 B1).
|
|
Some(disk) => disk.delete_volume(&bucket, force).await,
|
|
None => Err(DiskError::DiskNotFound),
|
|
}
|
|
});
|
|
}
|
|
|
|
let results = join_all(futures).await;
|
|
let mut errs = Vec::with_capacity(results.len());
|
|
let mut recreate = false;
|
|
for result in results {
|
|
match result {
|
|
Ok(()) => errs.push(None),
|
|
Err(err) => {
|
|
if matches!(err, DiskError::VolumeNotEmpty) {
|
|
recreate = true;
|
|
}
|
|
errs.push(Some(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
if recreate {
|
|
for (index, err) in errs.iter().enumerate() {
|
|
if err.is_none()
|
|
&& let Some(Some(disk)) = disks.get(index)
|
|
{
|
|
let _ = disk.make_volume(bucket).await;
|
|
}
|
|
}
|
|
return Err(Error::VolumeNotEmpty);
|
|
}
|
|
|
|
if let Some(err) = reduce_write_quorum_errs(&errs, BUCKET_OP_IGNORED_ERRS, write_quorum) {
|
|
return Err(err.into());
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|