mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +00:00
feat: Add --info command and refactor config module (#2234)
Signed-off-by: houseme <housemecn@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -60,6 +60,23 @@ impl GlobalReadiness {
|
|||||||
pub fn is_ready(&self) -> bool {
|
pub fn is_ready(&self) -> bool {
|
||||||
self.status.load(Ordering::SeqCst) == SystemStage::FullReady as u8
|
self.status.load(Ordering::SeqCst) == SystemStage::FullReady as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current system stage
|
||||||
|
/// # Returns
|
||||||
|
/// The current SystemStage of the service
|
||||||
|
pub fn current_stage(&self) -> SystemStage {
|
||||||
|
match self.status.load(Ordering::SeqCst) {
|
||||||
|
0 => SystemStage::Booting,
|
||||||
|
1 => SystemStage::StorageReady,
|
||||||
|
2 => SystemStage::IamReady,
|
||||||
|
3 => SystemStage::FullReady,
|
||||||
|
invalid => {
|
||||||
|
debug_assert!(false, "GlobalReadiness::current_stage: invalid status value {}", invalid);
|
||||||
|
// Fallback to the most conservative stage on invalid values
|
||||||
|
SystemStage::Booting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -131,9 +131,61 @@ pub const ENV_RUSTFS_ADDRESS: &str = "RUSTFS_ADDRESS";
|
|||||||
/// Environment variable for server volumes.
|
/// Environment variable for server volumes.
|
||||||
pub const ENV_RUSTFS_VOLUMES: &str = "RUSTFS_VOLUMES";
|
pub const ENV_RUSTFS_VOLUMES: &str = "RUSTFS_VOLUMES";
|
||||||
|
|
||||||
|
/// Environment variable for server access key.
|
||||||
|
pub const ENV_RUSTFS_ACCESS_KEY: &str = "RUSTFS_ACCESS_KEY";
|
||||||
|
|
||||||
|
/// Environment variable for server access key file.
|
||||||
|
pub const ENV_RUSTFS_ACCESS_KEY_FILE: &str = "RUSTFS_ACCESS_KEY_FILE";
|
||||||
|
|
||||||
|
/// Environment variable for server root user.
|
||||||
|
pub const ENV_RUSTFS_ROOT_USER: &str = "RUSTFS_ROOT_USER";
|
||||||
|
|
||||||
|
/// Environment variable for server secret key.
|
||||||
|
pub const ENV_RUSTFS_SECRET_KEY: &str = "RUSTFS_SECRET_KEY";
|
||||||
|
|
||||||
|
/// Environment variable for server secret key file.
|
||||||
|
pub const ENV_RUSTFS_SECRET_KEY_FILE: &str = "RUSTFS_SECRET_KEY_FILE";
|
||||||
|
|
||||||
|
/// Environment variable for server root password.
|
||||||
|
pub const ENV_RUSTFS_ROOT_PASSWORD: &str = "RUSTFS_ROOT_PASSWORD";
|
||||||
|
|
||||||
|
/// Environment variable for server OBS endpoint.
|
||||||
|
pub const ENV_RUSTFS_OBS_ENDPOINT: &str = "RUSTFS_OBS_ENDPOINT";
|
||||||
|
|
||||||
|
/// Environment variable for console server enable.
|
||||||
|
pub const ENV_RUSTFS_CONSOLE_ENABLE: &str = "RUSTFS_CONSOLE_ENABLE";
|
||||||
|
|
||||||
|
/// Environment variable for console server address.
|
||||||
|
pub const ENV_RUSTFS_CONSOLE_ADDRESS: &str = "RUSTFS_CONSOLE_ADDRESS";
|
||||||
|
|
||||||
/// Environment variable for server tls path.
|
/// Environment variable for server tls path.
|
||||||
pub const ENV_RUSTFS_TLS_PATH: &str = "RUSTFS_TLS_PATH";
|
pub const ENV_RUSTFS_TLS_PATH: &str = "RUSTFS_TLS_PATH";
|
||||||
|
|
||||||
|
/// Environment variable for server KMS enable.
|
||||||
|
pub const ENV_RUSTFS_KMS_ENABLE: &str = "RUSTFS_KMS_ENABLE";
|
||||||
|
|
||||||
|
/// Default KMS enable for server-side encryption
|
||||||
|
/// This is the default value for enabling KMS encryption for server-side encryption.
|
||||||
|
/// Default value: false
|
||||||
|
pub const DEFAULT_KMS_ENABLE: bool = false;
|
||||||
|
|
||||||
|
/// Environment variable for server KMS backend.
|
||||||
|
pub const ENV_RUSTFS_KMS_BACKEND: &str = "RUSTFS_KMS_BACKEND";
|
||||||
|
|
||||||
|
/// Default KMS backend for server-side encryption
|
||||||
|
/// This is the default KMS backend for server-side encryption.
|
||||||
|
/// Default value: local
|
||||||
|
pub const DEFAULT_KMS_BACKEND: &str = "local";
|
||||||
|
|
||||||
|
/// Environment variable for selecting the buffer profile used for adaptive buffer sizing.
|
||||||
|
pub const ENV_RUSTFS_BUFFER_PROFILE: &str = "RUSTFS_BUFFER_PROFILE";
|
||||||
|
|
||||||
|
/// Default buffer profile for adaptive buffer sizing
|
||||||
|
/// This is the default buffer profile for adaptive buffer sizing.
|
||||||
|
/// It is used to identify the workload profile for adaptive buffer sizing.
|
||||||
|
/// Default value: GeneralPurpose
|
||||||
|
pub const DEFAULT_BUFFER_PROFILE: &str = "GeneralPurpose";
|
||||||
|
|
||||||
/// Default value for the server TLS path if `ENV_RUSTFS_TLS_PATH` is not set.
|
/// Default value for the server TLS path if `ENV_RUSTFS_TLS_PATH` is not set.
|
||||||
pub const DEFAULT_RUSTFS_TLS_PATH: &str = "";
|
pub const DEFAULT_RUSTFS_TLS_PATH: &str = "";
|
||||||
|
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ pub(crate) mod runtime;
|
|||||||
pub(crate) mod scanner;
|
pub(crate) mod scanner;
|
||||||
pub(crate) mod targets;
|
pub(crate) mod targets;
|
||||||
pub(crate) mod tls;
|
pub(crate) mod tls;
|
||||||
|
pub(crate) mod workload;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Workload profile buffer configuration constants.
|
||||||
|
//!
|
||||||
|
//! This module defines environment variable keys and default values for
|
||||||
|
//! custom buffer profile configuration.
|
||||||
|
|
||||||
|
use crate::{KI_B, MI_B};
|
||||||
|
|
||||||
|
/// Environment variable for minimum buffer size
|
||||||
|
/// Default: 64KB (65536 bytes)
|
||||||
|
pub const ENV_RUSTFS_BUFFER_MIN_SIZE: &str = "RUSTFS_BUFFER_MIN_SIZE";
|
||||||
|
|
||||||
|
/// Environment variable for maximum buffer size
|
||||||
|
/// Default: 1MB (1048576 bytes)
|
||||||
|
pub const ENV_RUSTFS_BUFFER_MAX_SIZE: &str = "RUSTFS_BUFFER_MAX_SIZE";
|
||||||
|
|
||||||
|
/// Environment variable for default buffer size (used when file size is unknown)
|
||||||
|
/// Default: 256KB (262144 bytes)
|
||||||
|
pub const ENV_RUSTFS_BUFFER_DEFAULT_SIZE: &str = "RUSTFS_BUFFER_DEFAULT_SIZE";
|
||||||
|
|
||||||
|
/// Default minimum buffer size: 64KB
|
||||||
|
pub const DEFAULT_BUFFER_MIN_SIZE: usize = 64 * KI_B;
|
||||||
|
|
||||||
|
/// Default maximum buffer size: 1MB
|
||||||
|
pub const DEFAULT_BUFFER_MAX_SIZE: usize = MI_B;
|
||||||
|
|
||||||
|
/// Default buffer size for unknown file size: 256KB
|
||||||
|
pub const DEFAULT_BUFFER_UNKNOWN_SIZE: usize = 256 * KI_B;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::MI_B;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_values() {
|
||||||
|
assert_eq!(DEFAULT_BUFFER_MIN_SIZE, 65536); // 64KB
|
||||||
|
assert_eq!(DEFAULT_BUFFER_MAX_SIZE, 1048576); // 1MB
|
||||||
|
assert_eq!(DEFAULT_BUFFER_UNKNOWN_SIZE, 262144); // 256KB
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_constants() {
|
||||||
|
assert_eq!(KI_B, 1024);
|
||||||
|
assert_eq!(MI_B, 1024 * 1024);
|
||||||
|
assert_eq!(64 * KI_B, DEFAULT_BUFFER_MIN_SIZE);
|
||||||
|
assert_eq!(MI_B, DEFAULT_BUFFER_MAX_SIZE);
|
||||||
|
assert_eq!(256 * KI_B, DEFAULT_BUFFER_UNKNOWN_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_var_names() {
|
||||||
|
assert_eq!(ENV_RUSTFS_BUFFER_MIN_SIZE, "RUSTFS_BUFFER_MIN_SIZE");
|
||||||
|
assert_eq!(ENV_RUSTFS_BUFFER_MAX_SIZE, "RUSTFS_BUFFER_MAX_SIZE");
|
||||||
|
assert_eq!(ENV_RUSTFS_BUFFER_DEFAULT_SIZE, "RUSTFS_BUFFER_DEFAULT_SIZE");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,8 @@ pub use constants::targets::*;
|
|||||||
#[cfg(feature = "constants")]
|
#[cfg(feature = "constants")]
|
||||||
pub use constants::tls::*;
|
pub use constants::tls::*;
|
||||||
#[cfg(feature = "constants")]
|
#[cfg(feature = "constants")]
|
||||||
|
pub use constants::workload::*;
|
||||||
|
#[cfg(feature = "constants")]
|
||||||
pub mod oidc {
|
pub mod oidc {
|
||||||
pub use super::constants::oidc::*;
|
pub use super::constants::oidc::*;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::admin::handlers::health::{HealthProbe, build_component_details, collect_dependency_readiness, health_check_state};
|
use crate::admin::handlers::health::{HealthProbe, build_component_details, collect_dependency_readiness, health_check_state};
|
||||||
use crate::config::build;
|
|
||||||
use crate::license::get_license;
|
use crate::license::get_license;
|
||||||
use crate::server::{CONSOLE_PREFIX, FAVICON_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, RUSTFS_ADMIN_PREFIX};
|
use crate::server::{CONSOLE_PREFIX, FAVICON_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, RUSTFS_ADMIN_PREFIX};
|
||||||
|
use crate::version::build;
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
body::Body,
|
body::Body,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
//! This module introduces explicit dependency injection entry points
|
//! This module introduces explicit dependency injection entry points
|
||||||
//! for storage, IAM, and KMS handles.
|
//! for storage, IAM, and KMS handles.
|
||||||
|
|
||||||
use crate::config::workload_profiles::{RustFSBufferConfig, get_global_buffer_config};
|
use crate::config::{RustFSBufferConfig, get_global_buffer_config};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rustfs_ecstore::bucket::metadata_sys::{BucketMetadataSys, get_global_bucket_metadata_sys};
|
use rustfs_ecstore::bucket::metadata_sys::{BucketMetadataSys, get_global_bucket_metadata_sys};
|
||||||
use rustfs_ecstore::config::{Config, get_global_server_config};
|
use rustfs_ecstore::config::{Config, get_global_server_config};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
//! Object application use-case contracts.
|
//! Object application use-case contracts.
|
||||||
|
|
||||||
use crate::app::context::{AppContext, default_notify_interface, get_global_app_context};
|
use crate::app::context::{AppContext, default_notify_interface, get_global_app_context};
|
||||||
use crate::config::workload_profiles::RustFSBufferConfig;
|
use crate::config::RustFSBufferConfig;
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use crate::storage::access::{authorize_request, has_bypass_governance_header, req_info_mut};
|
use crate::storage::access::{authorize_request, has_bypass_governance_header, req_info_mut};
|
||||||
use crate::storage::concurrency::{
|
use crate::storage::concurrency::{
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! CLI definitions and parsing logic.
|
||||||
|
//!
|
||||||
|
//! This module contains the command-line interface definitions including:
|
||||||
|
//! - `Cli`: Main CLI parser
|
||||||
|
//! - `Commands`: Subcommands (Server, Info)
|
||||||
|
//! - `ServerOpts`: Server subcommand options
|
||||||
|
//! - `InfoOpts`: Info subcommand options
|
||||||
|
//! - `InfoType`: Information type enum
|
||||||
|
//! - `CommandResult`: Result of parsing command line arguments
|
||||||
|
|
||||||
|
use crate::version::build;
|
||||||
|
use clap::builder::NonEmptyStringValueParser;
|
||||||
|
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||||
|
use const_str::concat;
|
||||||
|
use rustfs_config::{DEFAULT_ADDRESS, DEFAULT_CONSOLE_ADDRESS, DEFAULT_CONSOLE_ENABLE, DEFAULT_OBS_ENDPOINT, ENV_RUSTFS_VOLUMES};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
// build module is re-exported from crate::build
|
||||||
|
|
||||||
|
#[allow(clippy::const_is_empty)]
|
||||||
|
pub(super) const SHORT_VERSION: &str = {
|
||||||
|
if !build::TAG.is_empty() {
|
||||||
|
build::TAG
|
||||||
|
} else if !build::SHORT_COMMIT.is_empty() {
|
||||||
|
concat!("@", build::SHORT_COMMIT)
|
||||||
|
} else {
|
||||||
|
build::PKG_VERSION
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) const LONG_VERSION: &str = concat!(
|
||||||
|
concat!(SHORT_VERSION, "\n"),
|
||||||
|
concat!("build time : ", build::BUILD_TIME, "\n"),
|
||||||
|
concat!("build profile: ", build::BUILD_RUST_CHANNEL, "\n"),
|
||||||
|
concat!("build os : ", build::BUILD_OS, "\n"),
|
||||||
|
concat!("rust version : ", build::RUST_VERSION, "\n"),
|
||||||
|
concat!("rust channel : ", build::RUST_CHANNEL, "\n"),
|
||||||
|
concat!("git branch : ", build::BRANCH, "\n"),
|
||||||
|
concat!("git commit : ", build::COMMIT_HASH, "\n"),
|
||||||
|
concat!("git tag : ", build::TAG, "\n"),
|
||||||
|
concat!("git status :\n", build::GIT_STATUS_FILE),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Known subcommands. When the first arg matches one of these, it is treated as a subcommand.
|
||||||
|
pub const KNOWN_SUBCOMMANDS: &[&str] = &["server", "info"];
|
||||||
|
|
||||||
|
/// Preprocess argv for legacy compatibility: `rustfs <volume>` and `rustfs --address ...` are
|
||||||
|
/// treated as `rustfs server <volume>` and `rustfs server --address ...` respectively.
|
||||||
|
/// Also: `rustfs` with no args becomes `rustfs server` (volumes from env), and `rustfs --info`
|
||||||
|
/// is treated as `rustfs info`.
|
||||||
|
pub fn preprocess_args_for_legacy(args: Vec<String>) -> Vec<String> {
|
||||||
|
if args.len() < 2 {
|
||||||
|
// rustfs -> rustfs server (volumes from RUSTFS_VOLUMES env)
|
||||||
|
return vec![args[0].clone(), "server".to_string()];
|
||||||
|
}
|
||||||
|
let first = &args[1];
|
||||||
|
// If first arg looks like a subcommand, do nothing
|
||||||
|
if KNOWN_SUBCOMMANDS.contains(&first.as_str()) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
// If first arg is --info, treat it as info subcommand
|
||||||
|
if first == "--info" {
|
||||||
|
let mut out = vec![args[0].clone(), "info".to_string()];
|
||||||
|
out.extend(args[2..].iter().cloned());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// If first arg is a global flag (--help, --version), do nothing
|
||||||
|
if first == "--help" || first == "-h" || first == "--version" || first == "-V" {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
// Legacy: rustfs <volume> or rustfs --address ... -> rustfs server <volume|--address ...>
|
||||||
|
let mut out = vec![args[0].clone(), "server".to_string()];
|
||||||
|
out.extend(args[1..].iter().cloned());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main CLI parser
|
||||||
|
#[derive(Parser, Clone)]
|
||||||
|
#[command(name = "rustfs", version = SHORT_VERSION, long_version = LONG_VERSION)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Option<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Available subcommands
|
||||||
|
#[derive(Subcommand, Clone)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// Start the object storage server (default when no subcommand is given)
|
||||||
|
Server(Box<ServerOpts>),
|
||||||
|
/// Display system information
|
||||||
|
Info(InfoOpts),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information type to display
|
||||||
|
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum InfoType {
|
||||||
|
/// System basic information (OS, architecture, hostname, etc.)
|
||||||
|
System,
|
||||||
|
/// Runtime information (PID, memory, CPU, threads, etc.)
|
||||||
|
Runtime,
|
||||||
|
/// Build information (version, build time, git info, etc.)
|
||||||
|
Build,
|
||||||
|
/// Current configuration information
|
||||||
|
Config,
|
||||||
|
/// Dependency library versions
|
||||||
|
Deps,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Info subcommand options
|
||||||
|
#[derive(Args, Clone)]
|
||||||
|
pub struct InfoOpts {
|
||||||
|
/// Display all information types
|
||||||
|
#[arg(long, conflicts_with = "info_type")]
|
||||||
|
pub all: bool,
|
||||||
|
|
||||||
|
/// Type of information to display
|
||||||
|
#[arg(value_enum, conflicts_with = "all")]
|
||||||
|
pub info_type: Option<InfoType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server subcommand options
|
||||||
|
#[derive(Args, Clone)]
|
||||||
|
pub struct ServerOpts {
|
||||||
|
/// DIR points to a directory on a filesystem.
|
||||||
|
#[arg(
|
||||||
|
required = true,
|
||||||
|
env = "RUSTFS_VOLUMES",
|
||||||
|
value_delimiter = ' ',
|
||||||
|
value_parser = NonEmptyStringValueParser::new()
|
||||||
|
)]
|
||||||
|
pub volumes: Vec<String>,
|
||||||
|
|
||||||
|
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = rustfs_config::DEFAULT_ADDRESS.to_string(),
|
||||||
|
env = "RUSTFS_ADDRESS"
|
||||||
|
)]
|
||||||
|
pub address: String,
|
||||||
|
|
||||||
|
/// Domain name used for virtual-hosted-style requests.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
env = "RUSTFS_SERVER_DOMAINS",
|
||||||
|
value_delimiter = ',',
|
||||||
|
value_parser = NonEmptyStringValueParser::new()
|
||||||
|
)]
|
||||||
|
pub server_domains: Vec<String>,
|
||||||
|
|
||||||
|
/// Access key used for authentication.
|
||||||
|
#[arg(long, env = "RUSTFS_ACCESS_KEY", group = "access-key")]
|
||||||
|
pub access_key: Option<String>,
|
||||||
|
|
||||||
|
/// Access key stored in a file used for authentication.
|
||||||
|
#[arg(long, env = "RUSTFS_ACCESS_KEY_FILE", group = "access-key")]
|
||||||
|
pub access_key_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Secret key used for authentication.
|
||||||
|
#[arg(long, env = "RUSTFS_SECRET_KEY", group = "secret-key")]
|
||||||
|
pub secret_key: Option<String>,
|
||||||
|
|
||||||
|
/// Secret key stored in a file used for authentication.
|
||||||
|
#[arg(long, env = "RUSTFS_SECRET_KEY_FILE", group = "secret-key")]
|
||||||
|
pub secret_key_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Enable console server
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = rustfs_config::DEFAULT_CONSOLE_ENABLE,
|
||||||
|
env = "RUSTFS_CONSOLE_ENABLE"
|
||||||
|
)]
|
||||||
|
pub console_enable: bool,
|
||||||
|
|
||||||
|
/// Console server bind address
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = rustfs_config::DEFAULT_CONSOLE_ADDRESS.to_string(),
|
||||||
|
env = "RUSTFS_CONSOLE_ADDRESS"
|
||||||
|
)]
|
||||||
|
pub console_address: String,
|
||||||
|
|
||||||
|
/// Observability endpoint for trace, metrics and logs,only support grpc mode.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(),
|
||||||
|
env = "RUSTFS_OBS_ENDPOINT"
|
||||||
|
)]
|
||||||
|
pub obs_endpoint: String,
|
||||||
|
|
||||||
|
/// tls path for rustfs API and console.
|
||||||
|
#[arg(long, env = "RUSTFS_TLS_PATH")]
|
||||||
|
pub tls_path: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long, env = "RUSTFS_LICENSE")]
|
||||||
|
pub license: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long, env = "RUSTFS_REGION")]
|
||||||
|
pub region: Option<String>,
|
||||||
|
|
||||||
|
/// Enable KMS encryption for server-side encryption
|
||||||
|
#[arg(long, default_value_t = false, env = "RUSTFS_KMS_ENABLE")]
|
||||||
|
pub kms_enable: bool,
|
||||||
|
|
||||||
|
/// KMS backend type (local or vault)
|
||||||
|
#[arg(long, default_value_t = rustfs_config::DEFAULT_KMS_BACKEND.to_string(), env = "RUSTFS_KMS_BACKEND")]
|
||||||
|
pub kms_backend: String,
|
||||||
|
|
||||||
|
/// KMS key directory for local backend
|
||||||
|
#[arg(long, env = "RUSTFS_KMS_KEY_DIR")]
|
||||||
|
pub kms_key_dir: Option<String>,
|
||||||
|
|
||||||
|
/// Vault address for vault backend
|
||||||
|
#[arg(long, env = "RUSTFS_KMS_VAULT_ADDRESS")]
|
||||||
|
pub kms_vault_address: Option<String>,
|
||||||
|
|
||||||
|
/// Vault token for vault backend
|
||||||
|
#[arg(long, env = "RUSTFS_KMS_VAULT_TOKEN")]
|
||||||
|
pub kms_vault_token: Option<String>,
|
||||||
|
|
||||||
|
/// Default KMS key ID for encryption
|
||||||
|
#[arg(long, env = "RUSTFS_KMS_DEFAULT_KEY_ID")]
|
||||||
|
pub kms_default_key_id: Option<String>,
|
||||||
|
|
||||||
|
/// Disable adaptive buffer sizing with workload profiles
|
||||||
|
/// Set this flag to use legacy fixed-size buffer behavior from PR #869
|
||||||
|
#[arg(long, default_value_t = false, env = "RUSTFS_BUFFER_PROFILE_DISABLE")]
|
||||||
|
pub buffer_profile_disable: bool,
|
||||||
|
|
||||||
|
/// Workload profile for adaptive buffer sizing
|
||||||
|
/// Options: GeneralPurpose, AiTraining, DataAnalytics, WebWorkload, IndustrialIoT, SecureStorage
|
||||||
|
#[arg(long, default_value_t = rustfs_config::DEFAULT_BUFFER_PROFILE.to_string(), env = "RUSTFS_BUFFER_PROFILE")]
|
||||||
|
pub buffer_profile: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of parsing command line arguments
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum CommandResult {
|
||||||
|
/// Server command with configuration
|
||||||
|
Server(Box<super::Config>),
|
||||||
|
/// Info command with options
|
||||||
|
Info(InfoOpts),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create default ServerOpts from environment variables
|
||||||
|
pub fn default_server_opts() -> ServerOpts {
|
||||||
|
ServerOpts {
|
||||||
|
volumes: std::env::var(ENV_RUSTFS_VOLUMES)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(' ')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect(),
|
||||||
|
address: DEFAULT_ADDRESS.to_string(),
|
||||||
|
server_domains: vec![],
|
||||||
|
access_key: None,
|
||||||
|
access_key_file: None,
|
||||||
|
secret_key: None,
|
||||||
|
secret_key_file: None,
|
||||||
|
console_enable: DEFAULT_CONSOLE_ENABLE,
|
||||||
|
console_address: DEFAULT_CONSOLE_ADDRESS.to_string(),
|
||||||
|
obs_endpoint: DEFAULT_OBS_ENDPOINT.to_string(),
|
||||||
|
tls_path: None,
|
||||||
|
license: None,
|
||||||
|
region: None,
|
||||||
|
kms_enable: false,
|
||||||
|
kms_backend: "local".to_string(),
|
||||||
|
kms_key_dir: None,
|
||||||
|
kms_vault_address: None,
|
||||||
|
kms_vault_token: None,
|
||||||
|
kms_default_key_id: None,
|
||||||
|
buffer_profile_disable: false,
|
||||||
|
buffer_profile: "GeneralPurpose".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Server configuration.
|
||||||
|
//!
|
||||||
|
//! This module contains the `Config` struct which holds the final server configuration
|
||||||
|
//! after processing command line arguments, environment variables, and files.
|
||||||
|
|
||||||
|
use super::Opt;
|
||||||
|
use crate::apply_external_env_compat;
|
||||||
|
use rustfs_config::{ENV_RUSTFS_ROOT_PASSWORD, ENV_RUSTFS_ROOT_USER, RUSTFS_REGION};
|
||||||
|
use rustfs_credentials::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, Masked};
|
||||||
|
|
||||||
|
/// Helper function to resolve credentials from multiple sources with precedence:
|
||||||
|
/// 1. Inline value (if provided)
|
||||||
|
/// 2. File value (if provided, read the content of the file)
|
||||||
|
/// 3. Environment variable (if set)
|
||||||
|
/// 4. Default value (if none of the above are provided)
|
||||||
|
pub(crate) fn resolve_credential<T: AsRef<std::path::Path>>(
|
||||||
|
inline_value: Option<String>,
|
||||||
|
file_value: Option<T>,
|
||||||
|
env_key: &str,
|
||||||
|
default_value: &str,
|
||||||
|
) -> std::io::Result<String> {
|
||||||
|
let value = inline_value
|
||||||
|
.map(Ok)
|
||||||
|
.or_else(|| file_value.map(std::fs::read_to_string))
|
||||||
|
.or_else(|| rustfs_utils::get_env_opt_str(env_key).map(Ok))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_else(|| default_value.to_string());
|
||||||
|
|
||||||
|
Ok(value.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Server configuration.
|
||||||
|
///
|
||||||
|
/// This struct holds all configuration values needed to run the server.
|
||||||
|
/// It is created from `Opt` which is parsed from command line arguments.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
/// DIR points to a directory on a filesystem.
|
||||||
|
pub volumes: Vec<String>,
|
||||||
|
|
||||||
|
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
|
||||||
|
pub address: String,
|
||||||
|
|
||||||
|
/// Domain name used for virtual-hosted-style requests.
|
||||||
|
pub server_domains: Vec<String>,
|
||||||
|
|
||||||
|
/// Access key used for authentication.
|
||||||
|
pub access_key: String,
|
||||||
|
|
||||||
|
/// Secret key used for authentication.
|
||||||
|
pub secret_key: String,
|
||||||
|
|
||||||
|
/// Enable console server
|
||||||
|
pub console_enable: bool,
|
||||||
|
|
||||||
|
/// Console server bind address
|
||||||
|
pub console_address: String,
|
||||||
|
|
||||||
|
/// Observability endpoint for trace, metrics and logs,only support grpc mode.
|
||||||
|
pub obs_endpoint: String,
|
||||||
|
|
||||||
|
/// tls path for rustfs API and console.
|
||||||
|
pub tls_path: Option<String>,
|
||||||
|
|
||||||
|
/// License key for enterprise features
|
||||||
|
pub license: Option<String>,
|
||||||
|
|
||||||
|
/// Region for the server, used for signing and other region-specific behavior
|
||||||
|
pub region: Option<String>,
|
||||||
|
|
||||||
|
/// Enable KMS encryption for server-side encryption
|
||||||
|
pub kms_enable: bool,
|
||||||
|
|
||||||
|
/// KMS backend type (local or vault)
|
||||||
|
pub kms_backend: String,
|
||||||
|
|
||||||
|
/// KMS key directory for local backend
|
||||||
|
pub kms_key_dir: Option<String>,
|
||||||
|
|
||||||
|
/// Vault address for vault backend
|
||||||
|
pub kms_vault_address: Option<String>,
|
||||||
|
|
||||||
|
/// Vault token for vault backend
|
||||||
|
pub kms_vault_token: Option<String>,
|
||||||
|
|
||||||
|
/// Default KMS key ID for encryption
|
||||||
|
pub kms_default_key_id: Option<String>,
|
||||||
|
|
||||||
|
/// Disable adaptive buffer sizing with workload profiles
|
||||||
|
pub buffer_profile_disable: bool,
|
||||||
|
|
||||||
|
/// Workload profile for adaptive buffer sizing
|
||||||
|
pub buffer_profile: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Create Config from Opt
|
||||||
|
pub(super) fn from_opt(opt: Opt) -> std::io::Result<Self> {
|
||||||
|
let Opt {
|
||||||
|
volumes,
|
||||||
|
address,
|
||||||
|
server_domains,
|
||||||
|
access_key,
|
||||||
|
access_key_file,
|
||||||
|
secret_key,
|
||||||
|
secret_key_file,
|
||||||
|
console_enable,
|
||||||
|
console_address,
|
||||||
|
obs_endpoint,
|
||||||
|
tls_path,
|
||||||
|
license,
|
||||||
|
region,
|
||||||
|
kms_enable,
|
||||||
|
kms_backend,
|
||||||
|
kms_key_dir,
|
||||||
|
kms_vault_address,
|
||||||
|
kms_vault_token,
|
||||||
|
kms_default_key_id,
|
||||||
|
buffer_profile_disable,
|
||||||
|
buffer_profile,
|
||||||
|
} = opt;
|
||||||
|
|
||||||
|
let access_key = resolve_credential(access_key, access_key_file.as_ref(), ENV_RUSTFS_ROOT_USER, DEFAULT_ACCESS_KEY)?;
|
||||||
|
let secret_key = resolve_credential(secret_key, secret_key_file.as_ref(), ENV_RUSTFS_ROOT_PASSWORD, DEFAULT_SECRET_KEY)?;
|
||||||
|
|
||||||
|
// Region is optional, but if not set, we should default to "us-east-1" for signing compatibility with AWS S3 clients
|
||||||
|
let region = region.or_else(|| Some(RUSTFS_REGION.to_string()));
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
volumes,
|
||||||
|
address,
|
||||||
|
server_domains,
|
||||||
|
access_key,
|
||||||
|
secret_key,
|
||||||
|
console_enable,
|
||||||
|
console_address,
|
||||||
|
obs_endpoint,
|
||||||
|
tls_path,
|
||||||
|
license,
|
||||||
|
region,
|
||||||
|
kms_enable,
|
||||||
|
kms_backend,
|
||||||
|
kms_key_dir,
|
||||||
|
kms_vault_address,
|
||||||
|
kms_vault_token,
|
||||||
|
kms_default_key_id,
|
||||||
|
buffer_profile_disable,
|
||||||
|
buffer_profile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the command line arguments and environment arguments from [`Opt`] and convert them
|
||||||
|
/// into a ready to use [`Config`].
|
||||||
|
///
|
||||||
|
/// Supports both `rustfs <volume>` (legacy) and `rustfs server <volume>`.
|
||||||
|
///
|
||||||
|
/// This includes some intermediate checks for mutually exclusive options.
|
||||||
|
#[allow(dead_code)] // used in config_test
|
||||||
|
pub fn parse() -> std::io::Result<Self> {
|
||||||
|
let _ = apply_external_env_compat();
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let opt = Opt::parse_from(args);
|
||||||
|
Self::from_opt(opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Config {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Config")
|
||||||
|
.field("volumes", &self.volumes)
|
||||||
|
.field("address", &self.address)
|
||||||
|
.field("server_domains", &self.server_domains)
|
||||||
|
.field("access_key", &self.access_key)
|
||||||
|
.field("secret_key", &Masked(Some(&self.secret_key))) // Hide sensitive values
|
||||||
|
.field("console_enable", &self.console_enable)
|
||||||
|
.field("console_address", &self.console_address)
|
||||||
|
.field("obs_endpoint", &self.obs_endpoint)
|
||||||
|
.field("tls_path", &self.tls_path)
|
||||||
|
.field("license", &Masked(self.license.as_deref()))
|
||||||
|
.field("region", &self.region)
|
||||||
|
.field("kms_enable", &self.kms_enable)
|
||||||
|
.field("kms_backend", &self.kms_backend)
|
||||||
|
.field("kms_key_dir", &self.kms_key_dir)
|
||||||
|
.field("kms_vault_address", &self.kms_vault_address)
|
||||||
|
.field("kms_vault_token", &Masked(self.kms_vault_token.as_deref()))
|
||||||
|
.field("kms_default_key_id", &self.kms_default_key_id)
|
||||||
|
.field("buffer_profile_disable", &self.buffer_profile_disable)
|
||||||
|
.field("buffer_profile", &self.buffer_profile)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,656 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! System information display module.
|
||||||
|
//!
|
||||||
|
//! This module provides the `--info` command functionality for querying and displaying
|
||||||
|
//! various system information including system basics, runtime stats, build info,
|
||||||
|
//! configuration, and dependencies.
|
||||||
|
|
||||||
|
use super::{InfoOpts, InfoType};
|
||||||
|
use crate::version::build;
|
||||||
|
use rustfs_credentials::Masked;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// CPU information
|
||||||
|
struct CpuInfo {
|
||||||
|
/// Number of logical CPU cores
|
||||||
|
core_count: usize,
|
||||||
|
/// CPU brand/vendor name
|
||||||
|
brand: String,
|
||||||
|
/// CPU frequency in MHz
|
||||||
|
frequency_mhz: u64,
|
||||||
|
/// CPU usage percentage (0-100)
|
||||||
|
usage_percent: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpuInfo {
|
||||||
|
fn collect(sys: &sysinfo::System) -> Self {
|
||||||
|
let core_count = sys.cpus().len();
|
||||||
|
let brand = sys
|
||||||
|
.cpus()
|
||||||
|
.first()
|
||||||
|
.map(|c| c.brand().to_string())
|
||||||
|
.unwrap_or_else(|| "Unknown".to_string());
|
||||||
|
let frequency_mhz = sys.cpus().first().map(|c| c.frequency()).unwrap_or(0);
|
||||||
|
|
||||||
|
// Calculate average CPU usage
|
||||||
|
let usage_percent = if core_count > 0 {
|
||||||
|
sys.cpus().iter().map(|c| c.cpu_usage() as f64).sum::<f64>() / core_count as f64
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
core_count,
|
||||||
|
brand,
|
||||||
|
frequency_mhz,
|
||||||
|
usage_percent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"CPU Cores: {}\n\
|
||||||
|
CPU Brand: {}\n\
|
||||||
|
CPU Frequency: {} MHz\n\
|
||||||
|
CPU Usage: {:.1}%",
|
||||||
|
self.core_count, self.brand, self.frequency_mhz, self.usage_percent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Memory information
|
||||||
|
struct MemoryInfo {
|
||||||
|
/// Total system memory in bytes
|
||||||
|
total_bytes: u64,
|
||||||
|
/// Used memory in bytes
|
||||||
|
used_bytes: u64,
|
||||||
|
/// Available/free memory in bytes
|
||||||
|
available_bytes: u64,
|
||||||
|
/// Total swap memory in bytes
|
||||||
|
total_swap_bytes: u64,
|
||||||
|
/// Used swap memory in bytes
|
||||||
|
used_swap_bytes: u64,
|
||||||
|
/// Memory usage percentage
|
||||||
|
usage_percent: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryInfo {
|
||||||
|
fn collect(sys: &sysinfo::System) -> Self {
|
||||||
|
let total_bytes = sys.total_memory();
|
||||||
|
let used_bytes = sys.used_memory();
|
||||||
|
let available_bytes = sys.available_memory();
|
||||||
|
let total_swap_bytes = sys.total_swap();
|
||||||
|
let used_swap_bytes = sys.used_swap();
|
||||||
|
|
||||||
|
let usage_percent = if total_bytes > 0 {
|
||||||
|
(used_bytes as f64 / total_bytes as f64) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
total_bytes,
|
||||||
|
used_bytes,
|
||||||
|
available_bytes,
|
||||||
|
total_swap_bytes,
|
||||||
|
used_swap_bytes,
|
||||||
|
usage_percent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_bytes(bytes: u64) -> String {
|
||||||
|
const KB: u64 = 1024;
|
||||||
|
const MB: u64 = KB * 1024;
|
||||||
|
const GB: u64 = MB * 1024;
|
||||||
|
const TB: u64 = GB * 1024;
|
||||||
|
|
||||||
|
if bytes >= TB {
|
||||||
|
format!("{:.2} TB", bytes as f64 / TB as f64)
|
||||||
|
} else if bytes >= GB {
|
||||||
|
format!("{:.2} GB", bytes as f64 / GB as f64)
|
||||||
|
} else if bytes >= MB {
|
||||||
|
format!("{:.2} MB", bytes as f64 / MB as f64)
|
||||||
|
} else if bytes >= KB {
|
||||||
|
format!("{:.2} KB", bytes as f64 / KB as f64)
|
||||||
|
} else {
|
||||||
|
format!("{} B", bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Total Memory: {}\n\
|
||||||
|
Used Memory: {} ({:.1}%)\n\
|
||||||
|
Available Memory: {}\n\
|
||||||
|
Total Swap: {}\n\
|
||||||
|
Used Swap: {}",
|
||||||
|
Self::format_bytes(self.total_bytes),
|
||||||
|
Self::format_bytes(self.used_bytes),
|
||||||
|
self.usage_percent,
|
||||||
|
Self::format_bytes(self.available_bytes),
|
||||||
|
Self::format_bytes(self.total_swap_bytes),
|
||||||
|
Self::format_bytes(self.used_swap_bytes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disk information
|
||||||
|
struct DiskInfo {
|
||||||
|
/// Disk mount point
|
||||||
|
mount_point: String,
|
||||||
|
/// Disk name/device
|
||||||
|
name: String,
|
||||||
|
/// File system type
|
||||||
|
file_system: String,
|
||||||
|
/// Total space in bytes
|
||||||
|
total_bytes: u64,
|
||||||
|
/// Used space in bytes
|
||||||
|
used_bytes: u64,
|
||||||
|
/// Available space in bytes
|
||||||
|
available_bytes: u64,
|
||||||
|
/// Usage percentage
|
||||||
|
usage_percent: f64,
|
||||||
|
/// Is this a removable disk
|
||||||
|
is_removable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiskInfo {
|
||||||
|
fn collect_all() -> Vec<Self> {
|
||||||
|
let disks = sysinfo::Disks::new_with_refreshed_list();
|
||||||
|
disks
|
||||||
|
.iter()
|
||||||
|
.map(|disk| {
|
||||||
|
let total_bytes = disk.total_space();
|
||||||
|
let available_bytes = disk.available_space();
|
||||||
|
let used_bytes = total_bytes.saturating_sub(available_bytes);
|
||||||
|
let usage_percent = if total_bytes > 0 {
|
||||||
|
(used_bytes as f64 / total_bytes as f64) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
mount_point: disk.mount_point().to_string_lossy().to_string(),
|
||||||
|
name: disk.name().to_string_lossy().to_string(),
|
||||||
|
file_system: format!("{:?}", disk.file_system()),
|
||||||
|
total_bytes,
|
||||||
|
used_bytes,
|
||||||
|
available_bytes,
|
||||||
|
usage_percent,
|
||||||
|
is_removable: disk.is_removable(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
format!(
|
||||||
|
" [{}] {}\n\
|
||||||
|
Mount: {}\n\
|
||||||
|
Type: {}\n\
|
||||||
|
Total: {}\n\
|
||||||
|
Used: {} ({:.1}%)\n\
|
||||||
|
Available: {}\n\
|
||||||
|
Removable: {}",
|
||||||
|
if self.is_removable { "R" } else { "F" },
|
||||||
|
self.name,
|
||||||
|
self.mount_point,
|
||||||
|
self.file_system,
|
||||||
|
MemoryInfo::format_bytes(self.total_bytes),
|
||||||
|
MemoryInfo::format_bytes(self.used_bytes),
|
||||||
|
self.usage_percent,
|
||||||
|
MemoryInfo::format_bytes(self.available_bytes),
|
||||||
|
self.is_removable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service disk information (disk where the service data is stored)
|
||||||
|
struct ServiceDiskInfo {
|
||||||
|
/// Disk mount point
|
||||||
|
mount_point: String,
|
||||||
|
/// Disk name/device
|
||||||
|
name: String,
|
||||||
|
/// File system type
|
||||||
|
file_system: String,
|
||||||
|
/// Total space in bytes
|
||||||
|
total_bytes: u64,
|
||||||
|
/// Used space in bytes
|
||||||
|
used_bytes: u64,
|
||||||
|
/// Available space in bytes
|
||||||
|
available_bytes: u64,
|
||||||
|
/// Usage percentage
|
||||||
|
usage_percent: f64,
|
||||||
|
/// Volume paths served by this disk
|
||||||
|
volume_paths: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceDiskInfo {
|
||||||
|
fn collect(volumes: &[String]) -> Option<Self> {
|
||||||
|
// Find the disk that contains the first volume path
|
||||||
|
let first_volume = volumes.first()?;
|
||||||
|
let volume_path = std::path::Path::new(first_volume);
|
||||||
|
|
||||||
|
// Find the disk that contains this volume
|
||||||
|
let disks = sysinfo::Disks::new_with_refreshed_list();
|
||||||
|
for disk in disks.iter() {
|
||||||
|
let mount_point = disk.mount_point();
|
||||||
|
if volume_path.starts_with(mount_point) {
|
||||||
|
let total_bytes = disk.total_space();
|
||||||
|
let available_bytes = disk.available_space();
|
||||||
|
let used_bytes = total_bytes.saturating_sub(available_bytes);
|
||||||
|
let usage_percent = if total_bytes > 0 {
|
||||||
|
(used_bytes as f64 / total_bytes as f64) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all volumes on this disk
|
||||||
|
let volume_paths: Vec<String> = volumes
|
||||||
|
.iter()
|
||||||
|
.filter(|v| std::path::Path::new(v).starts_with(mount_point))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Some(Self {
|
||||||
|
mount_point: mount_point.to_string_lossy().to_string(),
|
||||||
|
name: disk.name().to_string_lossy().to_string(),
|
||||||
|
file_system: format!("{:?}", disk.file_system()),
|
||||||
|
total_bytes,
|
||||||
|
used_bytes,
|
||||||
|
available_bytes,
|
||||||
|
usage_percent,
|
||||||
|
volume_paths,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
let volumes_str = if self.volume_paths.is_empty() {
|
||||||
|
"(none)".to_string()
|
||||||
|
} else {
|
||||||
|
self.volume_paths.join(", ")
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"=== Service Disk Information ===\n\
|
||||||
|
Disk: {}\n\
|
||||||
|
Mount Point: {}\n\
|
||||||
|
File System: {}\n\
|
||||||
|
Total Space: {}\n\
|
||||||
|
Used Space: {} ({:.1}%)\n\
|
||||||
|
Available Space: {}\n\
|
||||||
|
Served Volumes: {}",
|
||||||
|
self.name,
|
||||||
|
self.mount_point,
|
||||||
|
self.file_system,
|
||||||
|
MemoryInfo::format_bytes(self.total_bytes),
|
||||||
|
MemoryInfo::format_bytes(self.used_bytes),
|
||||||
|
self.usage_percent,
|
||||||
|
MemoryInfo::format_bytes(self.available_bytes),
|
||||||
|
volumes_str
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System basic information
|
||||||
|
struct SystemInfo {
|
||||||
|
os_type: String,
|
||||||
|
os_version: String,
|
||||||
|
architecture: String,
|
||||||
|
hostname: String,
|
||||||
|
kernel_version: String,
|
||||||
|
/// CPU information
|
||||||
|
cpu: CpuInfo,
|
||||||
|
/// Memory information
|
||||||
|
memory: MemoryInfo,
|
||||||
|
/// All disk information
|
||||||
|
disks: Vec<DiskInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemInfo {
|
||||||
|
fn collect() -> Self {
|
||||||
|
// Create system info collector
|
||||||
|
let mut sys = sysinfo::System::new_all();
|
||||||
|
sys.refresh_all();
|
||||||
|
sys.refresh_cpu_all();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
os_type: sysinfo::System::distribution_id(),
|
||||||
|
os_version: sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
architecture: std::env::consts::ARCH.to_string(),
|
||||||
|
hostname: sysinfo::System::host_name().unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
kernel_version: sysinfo::System::kernel_long_version(),
|
||||||
|
cpu: CpuInfo::collect(&sys),
|
||||||
|
memory: MemoryInfo::collect(&sys),
|
||||||
|
disks: DiskInfo::collect_all(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
let disks_str = if self.disks.is_empty() {
|
||||||
|
" (no disks found)".to_string()
|
||||||
|
} else {
|
||||||
|
self.disks.iter().map(|d| d.format()).collect::<Vec<_>>().join("\n")
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"=== System Information ===\n\
|
||||||
|
OS: {}\n\
|
||||||
|
OS Version: {}\n\
|
||||||
|
Architecture: {}\n\
|
||||||
|
Hostname: {}\n\
|
||||||
|
Kernel Version: {}\n\
|
||||||
|
\n\
|
||||||
|
=== CPU Information ===\n\
|
||||||
|
{}\n\
|
||||||
|
\n\
|
||||||
|
=== Memory Information ===\n\
|
||||||
|
{}\n\
|
||||||
|
\n\
|
||||||
|
=== Disk Information ===\n\
|
||||||
|
{}",
|
||||||
|
self.os_type,
|
||||||
|
self.os_version,
|
||||||
|
self.architecture,
|
||||||
|
self.hostname,
|
||||||
|
self.kernel_version,
|
||||||
|
self.cpu.format(),
|
||||||
|
self.memory.format(),
|
||||||
|
disks_str
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for SystemInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.format())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runtime information
|
||||||
|
struct RuntimeInfo {
|
||||||
|
process_id: u32,
|
||||||
|
memory_usage_mb: f64,
|
||||||
|
cpu_usage_percent: f64,
|
||||||
|
thread_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeInfo {
|
||||||
|
fn collect() -> Self {
|
||||||
|
let (process_id, memory_usage_mb, cpu_usage_percent) = if let Ok(pid) = sysinfo::get_current_pid() {
|
||||||
|
let mut sys = sysinfo::System::new();
|
||||||
|
sys.refresh_processes_specifics(
|
||||||
|
sysinfo::ProcessesToUpdate::Some(&[pid]),
|
||||||
|
true,
|
||||||
|
sysinfo::ProcessRefreshKind::everything(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let process = sys.process(pid);
|
||||||
|
let (memory_usage_mb, cpu_usage_percent) = if let Some(p) = process {
|
||||||
|
let memory = p.memory() as f64 / 1024.0 / 1024.0; // Convert to MB
|
||||||
|
let cpu = p.cpu_usage() as f64;
|
||||||
|
(memory, cpu)
|
||||||
|
} else {
|
||||||
|
(0.0, 0.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
(pid.as_u32(), memory_usage_mb, cpu_usage_percent)
|
||||||
|
} else {
|
||||||
|
// Failed to retrieve current PID; degrade gracefully by
|
||||||
|
// skipping per-process stats and using sentinel values.
|
||||||
|
(0, 0.0, 0.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// // Get available CPU parallelism (roughly, logical cores available to the process)
|
||||||
|
let cpu_parallelism = std::thread::available_parallelism().map(|p| p.get()).unwrap_or(1);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
process_id,
|
||||||
|
memory_usage_mb,
|
||||||
|
cpu_usage_percent,
|
||||||
|
thread_count: cpu_parallelism,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self) -> String {
|
||||||
|
let pid_display = if self.process_id == 0 {
|
||||||
|
"unknown".to_string()
|
||||||
|
} else {
|
||||||
|
self.process_id.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"=== Runtime Information ===\n\
|
||||||
|
Process ID: {}\n\
|
||||||
|
Memory Usage: {:.2} MB\n\
|
||||||
|
CPU Usage: {:.2}%\n\
|
||||||
|
CPU Parallelism (logical cores): {}",
|
||||||
|
pid_display, self.memory_usage_mb, self.cpu_usage_percent, self.thread_count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for RuntimeInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.format())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build information (using shadow-rs generated constants)
|
||||||
|
struct BuildInfo;
|
||||||
|
|
||||||
|
impl BuildInfo {
|
||||||
|
fn format() -> String {
|
||||||
|
format!(
|
||||||
|
"=== Build Information ===\n\
|
||||||
|
Version: {}\n\
|
||||||
|
Build Time: {}\n\
|
||||||
|
Build Profile: {}\n\
|
||||||
|
Build OS: {}\n\
|
||||||
|
Rust Version: {}\n\
|
||||||
|
Git Branch: {}\n\
|
||||||
|
Git Commit: {}\n\
|
||||||
|
Git Tag: {}\n\
|
||||||
|
Git Status: {}",
|
||||||
|
build::PKG_VERSION,
|
||||||
|
build::BUILD_TIME,
|
||||||
|
build::BUILD_RUST_CHANNEL,
|
||||||
|
build::BUILD_OS,
|
||||||
|
build::RUST_VERSION,
|
||||||
|
build::BRANCH,
|
||||||
|
build::COMMIT_HASH,
|
||||||
|
build::TAG,
|
||||||
|
build::GIT_STATUS_FILE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BuildInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", Self::format())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration information display
|
||||||
|
fn format_config_info() -> String {
|
||||||
|
// Get config snapshot for display (from global if initialized, otherwise from env)
|
||||||
|
let snapshot = super::get_config_snapshot_for_display();
|
||||||
|
|
||||||
|
// Get workload profile info
|
||||||
|
let workload_info = get_workload_profile_info();
|
||||||
|
|
||||||
|
// Mask the access key for display
|
||||||
|
let masked_access_key = &Masked(Some(&snapshot.access_key));
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"=== Configuration Information ===\n\
|
||||||
|
Server Address: {}\n\
|
||||||
|
Console Enable: {}\n\
|
||||||
|
Console Address: {}\n\
|
||||||
|
Region: {}\n\
|
||||||
|
Access Key: {}\n\
|
||||||
|
Secret Key: ****\n\
|
||||||
|
OBS Endpoint: {}\n\
|
||||||
|
TLS Path: {}\n\
|
||||||
|
KMS Enabled: {}\n\
|
||||||
|
KMS Backend: {}\n\
|
||||||
|
Buffer Profile: {}\n\
|
||||||
|
{}",
|
||||||
|
snapshot.address,
|
||||||
|
snapshot.console_enable,
|
||||||
|
snapshot.console_address,
|
||||||
|
snapshot.region.as_deref().unwrap_or("(not set)"),
|
||||||
|
masked_access_key,
|
||||||
|
if snapshot.obs_endpoint.is_empty() {
|
||||||
|
"(not set)"
|
||||||
|
} else {
|
||||||
|
&snapshot.obs_endpoint
|
||||||
|
},
|
||||||
|
snapshot.tls_path.as_deref().unwrap_or("(not set)"),
|
||||||
|
snapshot.kms_enable,
|
||||||
|
snapshot.kms_backend,
|
||||||
|
snapshot.buffer_profile,
|
||||||
|
workload_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get workload profile information from global buffer config
|
||||||
|
fn get_workload_profile_info() -> String {
|
||||||
|
use super::workload_profiles::{get_global_buffer_config, is_buffer_profile_enabled};
|
||||||
|
|
||||||
|
if !is_buffer_profile_enabled() {
|
||||||
|
return "Workload Profile: (disabled)".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = get_global_buffer_config();
|
||||||
|
let profile = config.workload_profile();
|
||||||
|
let name = config.workload_name();
|
||||||
|
let buffer_config = profile.config();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"Workload Profile: {}\n\
|
||||||
|
Buffer Min Size: {} bytes\n\
|
||||||
|
Buffer Max Size: {} bytes\n\
|
||||||
|
Default Unknown: {} bytes",
|
||||||
|
name, buffer_config.min_size, buffer_config.max_size, buffer_config.default_unknown
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dependency information
|
||||||
|
fn format_deps_info() -> String {
|
||||||
|
let mut output = String::from("=== Build Features ===\n");
|
||||||
|
|
||||||
|
// Check which features are enabled at compile time
|
||||||
|
let features = [
|
||||||
|
("metrics", cfg!(feature = "metrics"), "Metrics collection and reporting"),
|
||||||
|
("ftps", cfg!(feature = "ftps"), "FTPS protocol support"),
|
||||||
|
("swift", cfg!(feature = "swift"), "Swift storage backend"),
|
||||||
|
("webdav", cfg!(feature = "webdav"), "WebDAV protocol support"),
|
||||||
|
("license", cfg!(feature = "license"), "License validation"),
|
||||||
|
("full", cfg!(feature = "full"), "All features enabled"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let enabled_count = features.iter().filter(|(_, enabled, _)| *enabled).count();
|
||||||
|
output.push_str(&format!("Enabled Features: {}/{}\n\n", enabled_count, features.len()));
|
||||||
|
|
||||||
|
output.push_str("Feature Status:\n");
|
||||||
|
for (name, enabled, description) in features {
|
||||||
|
let status = if enabled { "[x]" } else { "[ ]" };
|
||||||
|
output.push_str(&format!(" {} {} - {}\n", status, name, description));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show default features info
|
||||||
|
output.push_str("\n--- Default Features ---\n");
|
||||||
|
output.push_str(" metrics (enabled by default)\n");
|
||||||
|
|
||||||
|
// Show feature dependencies
|
||||||
|
output.push_str("\n--- Feature Dependencies ---\n");
|
||||||
|
output.push_str(" full = metrics + ftps + swift + webdav\n");
|
||||||
|
output.push_str(" ftps -> rustfs-protocols/ftps\n");
|
||||||
|
output.push_str(" swift -> rustfs-protocols/swift\n");
|
||||||
|
output.push_str(" webdav -> rustfs-protocols/webdav\n");
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the info command
|
||||||
|
pub fn execute_info(opts: &InfoOpts) {
|
||||||
|
execute_info_with_volumes(opts, &[])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute info command with volume paths for service disk information
|
||||||
|
pub fn execute_info_with_volumes(opts: &InfoOpts, volumes: &[String]) {
|
||||||
|
let info_type = if opts.all {
|
||||||
|
None // None means display all
|
||||||
|
} else {
|
||||||
|
opts.info_type
|
||||||
|
};
|
||||||
|
|
||||||
|
match info_type {
|
||||||
|
None => {
|
||||||
|
// Display all information
|
||||||
|
println!("{}", SystemInfo::collect());
|
||||||
|
println!();
|
||||||
|
println!("{}", RuntimeInfo::collect());
|
||||||
|
println!();
|
||||||
|
println!("{}", BuildInfo);
|
||||||
|
println!();
|
||||||
|
println!("{}", format_config_info());
|
||||||
|
println!();
|
||||||
|
// Display service disk info if volumes are configured
|
||||||
|
if let Some(service_disk) = ServiceDiskInfo::collect(volumes) {
|
||||||
|
println!("{}", service_disk.format());
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
println!("{}", format_deps_info());
|
||||||
|
}
|
||||||
|
Some(InfoType::System) => {
|
||||||
|
println!("{}", SystemInfo::collect());
|
||||||
|
}
|
||||||
|
Some(InfoType::Runtime) => {
|
||||||
|
println!("{}", RuntimeInfo::collect());
|
||||||
|
}
|
||||||
|
Some(InfoType::Build) => {
|
||||||
|
println!("{}", BuildInfo);
|
||||||
|
}
|
||||||
|
Some(InfoType::Config) => {
|
||||||
|
println!("{}", format_config_info());
|
||||||
|
}
|
||||||
|
Some(InfoType::Deps) => {
|
||||||
|
println!("{}", format_deps_info());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_system_info_collect() {
|
||||||
|
let info = SystemInfo::collect();
|
||||||
|
assert!(!info.os_type.is_empty());
|
||||||
|
assert!(!info.architecture.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runtime_info_collect() {
|
||||||
|
let info = RuntimeInfo::collect();
|
||||||
|
assert!(info.process_id > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
-455
@@ -12,464 +12,50 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use clap::builder::NonEmptyStringValueParser;
|
//! Configuration module for RustFS.
|
||||||
use clap::{Args, Parser, Subcommand};
|
//!
|
||||||
use const_str::concat;
|
//! This module is organized into the following submodules:
|
||||||
use rustfs_config::RUSTFS_REGION;
|
//!
|
||||||
use rustfs_utils::{apply_external_env_compat, get_env_opt_str};
|
//! - [`cli`]: Command-line interface definitions (Cli, Commands, ServerOpts, InfoOpts)
|
||||||
use std::path::PathBuf;
|
//! - [`opt`]: Parsed server options (Opt) and parsing methods
|
||||||
use std::string::ToString;
|
//! - [`config_struct`]: Server configuration (Config)
|
||||||
|
//! - [`snapshot`]: Configuration snapshot for info command
|
||||||
|
//! - [`info`]: Info command execution
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! ```ignore
|
||||||
|
//! use rustfs::config::{Config, Opt, CommandResult};
|
||||||
|
//!
|
||||||
|
//! // Parse command line arguments
|
||||||
|
//! let result = Opt::parse_command(std::env::args())?;
|
||||||
|
//!
|
||||||
|
//! match result {
|
||||||
|
//! CommandResult::Server(config) => {
|
||||||
|
//! // Start server with config
|
||||||
|
//! }
|
||||||
|
//! CommandResult::Info(opts) => {
|
||||||
|
//! // Display info
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
shadow_rs::shadow!(build);
|
mod cli;
|
||||||
|
mod config_struct;
|
||||||
pub mod workload_profiles;
|
mod info;
|
||||||
|
mod opt;
|
||||||
|
mod snapshot;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod config_test;
|
mod config_test;
|
||||||
|
|
||||||
#[allow(clippy::const_is_empty)]
|
// Re-export public types
|
||||||
const SHORT_VERSION: &str = {
|
pub use cli::{CommandResult, InfoOpts, InfoType};
|
||||||
if !build::TAG.is_empty() {
|
pub use config_struct::Config;
|
||||||
build::TAG
|
pub use info::execute_info;
|
||||||
} else if !build::SHORT_COMMIT.is_empty() {
|
pub use opt::Opt;
|
||||||
concat!("@", build::SHORT_COMMIT)
|
pub use snapshot::{get_config_snapshot_for_display, init_config_snapshot};
|
||||||
} else {
|
|
||||||
build::PKG_VERSION
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const LONG_VERSION: &str = concat!(
|
// Re-export workload profiles
|
||||||
concat!(SHORT_VERSION, "\n"),
|
mod workload_profiles;
|
||||||
concat!("build time : ", build::BUILD_TIME, "\n"),
|
pub use workload_profiles::*;
|
||||||
concat!("build profile: ", build::BUILD_RUST_CHANNEL, "\n"),
|
|
||||||
concat!("build os : ", build::BUILD_OS, "\n"),
|
|
||||||
concat!("rust version : ", build::RUST_VERSION, "\n"),
|
|
||||||
concat!("rust channel : ", build::RUST_CHANNEL, "\n"),
|
|
||||||
concat!("git branch : ", build::BRANCH, "\n"),
|
|
||||||
concat!("git commit : ", build::COMMIT_HASH, "\n"),
|
|
||||||
concat!("git tag : ", build::TAG, "\n"),
|
|
||||||
concat!("git status :\n", build::GIT_STATUS_FILE),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Known subcommands. When the first arg matches one of these, it is treated as a subcommand.
|
|
||||||
const KNOWN_SUBCOMMANDS: &[&str] = &["server"];
|
|
||||||
|
|
||||||
/// Preprocess argv for legacy compatibility: `rustfs <volume>` and `rustfs --address ...` are
|
|
||||||
/// treated as `rustfs server <volume>` and `rustfs server --address ...` respectively.
|
|
||||||
/// Also: `rustfs` with no args becomes `rustfs server` (volumes from env).
|
|
||||||
fn preprocess_args_for_legacy(args: Vec<String>) -> Vec<String> {
|
|
||||||
if args.len() < 2 {
|
|
||||||
// rustfs -> rustfs server (volumes from RUSTFS_VOLUMES env)
|
|
||||||
return vec![args[0].clone(), "server".to_string()];
|
|
||||||
}
|
|
||||||
let first = &args[1];
|
|
||||||
// If first arg looks like a subcommand, do nothing
|
|
||||||
if KNOWN_SUBCOMMANDS.contains(&first.as_str()) {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
// If first arg is a global flag (--help, --version), do nothing
|
|
||||||
if first == "--help" || first == "-h" || first == "--version" || first == "-V" {
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
// Legacy: rustfs <volume> or rustfs --address ... -> rustfs server <volume|--address ...>
|
|
||||||
let mut out = vec![args[0].clone(), "server".to_string()];
|
|
||||||
out.extend(args[1..].iter().cloned());
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Clone)]
|
|
||||||
#[command(name = "rustfs", version = SHORT_VERSION, long_version = LONG_VERSION)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Option<Commands>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Clone)]
|
|
||||||
enum Commands {
|
|
||||||
/// Start the object storage server (default when no subcommand is given)
|
|
||||||
Server(ServerOpts),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Clone)]
|
|
||||||
struct ServerOpts {
|
|
||||||
/// DIR points to a directory on a filesystem.
|
|
||||||
#[arg(
|
|
||||||
required = true,
|
|
||||||
env = "RUSTFS_VOLUMES",
|
|
||||||
value_delimiter = ' ',
|
|
||||||
value_parser = NonEmptyStringValueParser::new()
|
|
||||||
)]
|
|
||||||
pub volumes: Vec<String>,
|
|
||||||
|
|
||||||
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
default_value_t = rustfs_config::DEFAULT_ADDRESS.to_string(),
|
|
||||||
env = "RUSTFS_ADDRESS"
|
|
||||||
)]
|
|
||||||
pub address: String,
|
|
||||||
|
|
||||||
/// Domain name used for virtual-hosted-style requests.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
env = "RUSTFS_SERVER_DOMAINS",
|
|
||||||
value_delimiter = ',',
|
|
||||||
value_parser = NonEmptyStringValueParser::new()
|
|
||||||
)]
|
|
||||||
pub server_domains: Vec<String>,
|
|
||||||
|
|
||||||
/// Access key used for authentication.
|
|
||||||
#[arg(long, env = "RUSTFS_ACCESS_KEY", group = "access-key")]
|
|
||||||
pub access_key: Option<String>,
|
|
||||||
|
|
||||||
/// Access key stored in a file used for authentication.
|
|
||||||
#[arg(long, env = "RUSTFS_ACCESS_KEY_FILE", group = "access-key")]
|
|
||||||
pub access_key_file: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Secret key used for authentication.
|
|
||||||
#[arg(long, env = "RUSTFS_SECRET_KEY", group = "secret-key")]
|
|
||||||
pub secret_key: Option<String>,
|
|
||||||
|
|
||||||
/// Secret key stored in a file used for authentication.
|
|
||||||
#[arg(long, env = "RUSTFS_SECRET_KEY_FILE", group = "secret-key")]
|
|
||||||
pub secret_key_file: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Enable console server
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
default_value_t = rustfs_config::DEFAULT_CONSOLE_ENABLE,
|
|
||||||
env = "RUSTFS_CONSOLE_ENABLE"
|
|
||||||
)]
|
|
||||||
pub console_enable: bool,
|
|
||||||
|
|
||||||
/// Console server bind address
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
default_value_t = rustfs_config::DEFAULT_CONSOLE_ADDRESS.to_string(),
|
|
||||||
env = "RUSTFS_CONSOLE_ADDRESS"
|
|
||||||
)]
|
|
||||||
pub console_address: String,
|
|
||||||
|
|
||||||
/// Observability endpoint for trace, metrics and logs,only support grpc mode.
|
|
||||||
#[arg(
|
|
||||||
long,
|
|
||||||
default_value_t = rustfs_config::DEFAULT_OBS_ENDPOINT.to_string(),
|
|
||||||
env = "RUSTFS_OBS_ENDPOINT"
|
|
||||||
)]
|
|
||||||
pub obs_endpoint: String,
|
|
||||||
|
|
||||||
/// tls path for rustfs API and console.
|
|
||||||
#[arg(long, env = "RUSTFS_TLS_PATH")]
|
|
||||||
pub tls_path: Option<String>,
|
|
||||||
|
|
||||||
#[arg(long, env = "RUSTFS_LICENSE")]
|
|
||||||
pub license: Option<String>,
|
|
||||||
|
|
||||||
#[arg(long, env = "RUSTFS_REGION")]
|
|
||||||
pub region: Option<String>,
|
|
||||||
|
|
||||||
/// Enable KMS encryption for server-side encryption
|
|
||||||
#[arg(long, default_value_t = false, env = "RUSTFS_KMS_ENABLE")]
|
|
||||||
pub kms_enable: bool,
|
|
||||||
|
|
||||||
/// KMS backend type (local or vault)
|
|
||||||
#[arg(long, default_value_t = String::from("local"), env = "RUSTFS_KMS_BACKEND")]
|
|
||||||
pub kms_backend: String,
|
|
||||||
|
|
||||||
/// KMS key directory for local backend
|
|
||||||
#[arg(long, env = "RUSTFS_KMS_KEY_DIR")]
|
|
||||||
pub kms_key_dir: Option<String>,
|
|
||||||
|
|
||||||
/// Vault address for vault backend
|
|
||||||
#[arg(long, env = "RUSTFS_KMS_VAULT_ADDRESS")]
|
|
||||||
pub kms_vault_address: Option<String>,
|
|
||||||
|
|
||||||
/// Vault token for vault backend
|
|
||||||
#[arg(long, env = "RUSTFS_KMS_VAULT_TOKEN")]
|
|
||||||
pub kms_vault_token: Option<String>,
|
|
||||||
|
|
||||||
/// Default KMS key ID for encryption
|
|
||||||
#[arg(long, env = "RUSTFS_KMS_DEFAULT_KEY_ID")]
|
|
||||||
pub kms_default_key_id: Option<String>,
|
|
||||||
|
|
||||||
/// Disable adaptive buffer sizing with workload profiles
|
|
||||||
/// Set this flag to use legacy fixed-size buffer behavior from PR #869
|
|
||||||
#[arg(long, default_value_t = false, env = "RUSTFS_BUFFER_PROFILE_DISABLE")]
|
|
||||||
pub buffer_profile_disable: bool,
|
|
||||||
|
|
||||||
/// Workload profile for adaptive buffer sizing
|
|
||||||
/// Options: GeneralPurpose, AiTraining, DataAnalytics, WebWorkload, IndustrialIoT, SecureStorage
|
|
||||||
#[arg(long, default_value_t = String::from("GeneralPurpose"), env = "RUSTFS_BUFFER_PROFILE")]
|
|
||||||
pub buffer_profile: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parsed server options. Public for tests and backward compatibility.
|
|
||||||
/// Use `Opt::parse_from` or `Config::parse()` to obtain.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Opt {
|
|
||||||
pub volumes: Vec<String>,
|
|
||||||
pub address: String,
|
|
||||||
pub server_domains: Vec<String>,
|
|
||||||
pub access_key: Option<String>,
|
|
||||||
pub access_key_file: Option<PathBuf>,
|
|
||||||
pub secret_key: Option<String>,
|
|
||||||
pub secret_key_file: Option<PathBuf>,
|
|
||||||
pub console_enable: bool,
|
|
||||||
pub console_address: String,
|
|
||||||
pub obs_endpoint: String,
|
|
||||||
pub tls_path: Option<String>,
|
|
||||||
pub license: Option<String>,
|
|
||||||
pub region: Option<String>,
|
|
||||||
pub kms_enable: bool,
|
|
||||||
pub kms_backend: String,
|
|
||||||
pub kms_key_dir: Option<String>,
|
|
||||||
pub kms_vault_address: Option<String>,
|
|
||||||
pub kms_vault_token: Option<String>,
|
|
||||||
pub kms_default_key_id: Option<String>,
|
|
||||||
pub buffer_profile_disable: bool,
|
|
||||||
pub buffer_profile: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opt {
|
|
||||||
fn from_server_opts(o: ServerOpts) -> Self {
|
|
||||||
Self {
|
|
||||||
volumes: o.volumes,
|
|
||||||
address: o.address,
|
|
||||||
server_domains: o.server_domains,
|
|
||||||
access_key: o.access_key,
|
|
||||||
access_key_file: o.access_key_file,
|
|
||||||
secret_key: o.secret_key,
|
|
||||||
secret_key_file: o.secret_key_file,
|
|
||||||
console_enable: o.console_enable,
|
|
||||||
console_address: o.console_address,
|
|
||||||
obs_endpoint: o.obs_endpoint,
|
|
||||||
tls_path: o.tls_path,
|
|
||||||
license: o.license,
|
|
||||||
region: o.region,
|
|
||||||
kms_enable: o.kms_enable,
|
|
||||||
kms_backend: o.kms_backend,
|
|
||||||
kms_key_dir: o.kms_key_dir,
|
|
||||||
kms_vault_address: o.kms_vault_address,
|
|
||||||
kms_vault_token: o.kms_vault_token,
|
|
||||||
kms_default_key_id: o.kms_default_key_id,
|
|
||||||
buffer_profile_disable: o.buffer_profile_disable,
|
|
||||||
buffer_profile: o.buffer_profile,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse from preprocessed args. Supports both `rustfs <volume>` and `rustfs server <volume>`.
|
|
||||||
pub fn parse_from<I, T>(args: I) -> Self
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = T>,
|
|
||||||
T: Into<std::ffi::OsString> + Clone,
|
|
||||||
{
|
|
||||||
let _ = apply_external_env_compat();
|
|
||||||
let args: Vec<String> = args.into_iter().map(|a| a.into().to_string_lossy().into_owned()).collect();
|
|
||||||
let args = preprocess_args_for_legacy(args);
|
|
||||||
let cli = Cli::parse_from(args);
|
|
||||||
let Commands::Server(opts) = cli.command.expect("server is the default subcommand");
|
|
||||||
Self::from_server_opts(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try parse from args, returns error on invalid input.
|
|
||||||
#[allow(dead_code)] // used in config_test
|
|
||||||
pub fn try_parse_from<I, T>(args: I) -> Result<Self, clap::Error>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = T>,
|
|
||||||
T: Into<std::ffi::OsString> + Clone,
|
|
||||||
{
|
|
||||||
let _ = apply_external_env_compat();
|
|
||||||
let args: Vec<String> = args.into_iter().map(|a| a.into().to_string_lossy().into_owned()).collect();
|
|
||||||
let args = preprocess_args_for_legacy(args);
|
|
||||||
let cli = Cli::try_parse_from(args)?;
|
|
||||||
let Commands::Server(opts) = cli.command.expect("server is the default subcommand");
|
|
||||||
Ok(Self::from_server_opts(opts))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse from env::args(). Used by Config::parse().
|
|
||||||
fn parse() -> Self {
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
|
||||||
Self::parse_from(args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
/// DIR points to a directory on a filesystem.
|
|
||||||
pub volumes: Vec<String>,
|
|
||||||
|
|
||||||
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
|
|
||||||
pub address: String,
|
|
||||||
|
|
||||||
/// Domain name used for virtual-hosted-style requests.
|
|
||||||
pub server_domains: Vec<String>,
|
|
||||||
|
|
||||||
/// Access key used for authentication.
|
|
||||||
pub access_key: String,
|
|
||||||
|
|
||||||
/// Secret key used for authentication.
|
|
||||||
pub secret_key: String,
|
|
||||||
|
|
||||||
/// Enable console server
|
|
||||||
pub console_enable: bool,
|
|
||||||
|
|
||||||
/// Console server bind address
|
|
||||||
pub console_address: String,
|
|
||||||
|
|
||||||
/// Observability endpoint for trace, metrics and logs,only support grpc mode.
|
|
||||||
pub obs_endpoint: String,
|
|
||||||
|
|
||||||
/// tls path for rustfs API and console.
|
|
||||||
pub tls_path: Option<String>,
|
|
||||||
|
|
||||||
/// License key for enterprise features
|
|
||||||
pub license: Option<String>,
|
|
||||||
|
|
||||||
/// Region for the server, used for signing and other region-specific behavior
|
|
||||||
pub region: Option<String>,
|
|
||||||
|
|
||||||
/// Enable KMS encryption for server-side encryption
|
|
||||||
pub kms_enable: bool,
|
|
||||||
|
|
||||||
/// KMS backend type (local or vault)
|
|
||||||
pub kms_backend: String,
|
|
||||||
|
|
||||||
/// KMS key directory for local backend
|
|
||||||
pub kms_key_dir: Option<String>,
|
|
||||||
|
|
||||||
/// Vault address for vault backend
|
|
||||||
pub kms_vault_address: Option<String>,
|
|
||||||
|
|
||||||
/// Vault token for vault backend
|
|
||||||
pub kms_vault_token: Option<String>,
|
|
||||||
|
|
||||||
/// Default KMS key ID for encryption
|
|
||||||
pub kms_default_key_id: Option<String>,
|
|
||||||
|
|
||||||
/// Disable adaptive buffer sizing with workload profiles
|
|
||||||
/// Set this flag to use legacy fixed-size buffer behavior from PR #869
|
|
||||||
pub buffer_profile_disable: bool,
|
|
||||||
|
|
||||||
/// Workload profile for adaptive buffer sizing
|
|
||||||
/// Options: GeneralPurpose, AiTraining, DataAnalytics, WebWorkload, IndustrialIoT, SecureStorage
|
|
||||||
pub buffer_profile: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn from_opt(opt: Opt) -> std::io::Result<Self> {
|
|
||||||
let Opt {
|
|
||||||
volumes,
|
|
||||||
address,
|
|
||||||
server_domains,
|
|
||||||
access_key,
|
|
||||||
access_key_file,
|
|
||||||
secret_key,
|
|
||||||
secret_key_file,
|
|
||||||
console_enable,
|
|
||||||
console_address,
|
|
||||||
obs_endpoint,
|
|
||||||
tls_path,
|
|
||||||
license,
|
|
||||||
region,
|
|
||||||
kms_enable,
|
|
||||||
kms_backend,
|
|
||||||
kms_key_dir,
|
|
||||||
kms_vault_address,
|
|
||||||
kms_vault_token,
|
|
||||||
kms_default_key_id,
|
|
||||||
buffer_profile_disable,
|
|
||||||
buffer_profile,
|
|
||||||
} = opt;
|
|
||||||
|
|
||||||
let access_key = access_key
|
|
||||||
.map(Ok)
|
|
||||||
.or_else(|| {
|
|
||||||
let path = access_key_file.as_ref()?;
|
|
||||||
Some(std::fs::read_to_string(path))
|
|
||||||
})
|
|
||||||
.or_else(|| get_env_opt_str("RUSTFS_ROOT_USER").map(Ok))
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
// neither argument was specified ... using default
|
|
||||||
rustfs_credentials::DEFAULT_ACCESS_KEY.to_string()
|
|
||||||
})
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let secret_key = secret_key
|
|
||||||
.map(Ok)
|
|
||||||
.or_else(|| {
|
|
||||||
let path = secret_key_file.as_ref()?;
|
|
||||||
Some(std::fs::read_to_string(path))
|
|
||||||
})
|
|
||||||
.or_else(|| get_env_opt_str("RUSTFS_ROOT_PASSWORD").map(Ok))
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
// neither argument was specified ... using default
|
|
||||||
rustfs_credentials::DEFAULT_SECRET_KEY.to_string()
|
|
||||||
})
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
// Region is optional, but if not set, we should default to "us-east-1" for signing compatibility with AWS S3 clients
|
|
||||||
let region = region.or_else(|| Some(RUSTFS_REGION.to_string()));
|
|
||||||
|
|
||||||
Ok(Config {
|
|
||||||
volumes,
|
|
||||||
address,
|
|
||||||
server_domains,
|
|
||||||
access_key,
|
|
||||||
secret_key,
|
|
||||||
console_enable,
|
|
||||||
console_address,
|
|
||||||
obs_endpoint,
|
|
||||||
tls_path,
|
|
||||||
license,
|
|
||||||
region,
|
|
||||||
kms_enable,
|
|
||||||
kms_backend,
|
|
||||||
kms_key_dir,
|
|
||||||
kms_vault_address,
|
|
||||||
kms_vault_token,
|
|
||||||
kms_default_key_id,
|
|
||||||
buffer_profile_disable,
|
|
||||||
buffer_profile,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse the command line arguments and environment arguments from [`Opt`] and convert them
|
|
||||||
/// into a ready to use [`Config`].
|
|
||||||
///
|
|
||||||
/// Supports both `rustfs <volume>` (legacy) and `rustfs server <volume>`.
|
|
||||||
///
|
|
||||||
/// This includes some intermediate checks for mutually exclusive options.
|
|
||||||
pub fn parse() -> std::io::Result<Self> {
|
|
||||||
Self::from_opt(Opt::parse())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Config {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Config")
|
|
||||||
.field("volumes", &self.volumes)
|
|
||||||
.field("address", &self.address)
|
|
||||||
.field("server_domains", &self.server_domains)
|
|
||||||
.field("access_key", &self.access_key)
|
|
||||||
.field("secret_key", &rustfs_credentials::Masked(Some(&self.secret_key))) // Hide sensitive values
|
|
||||||
.field("console_enable", &self.console_enable)
|
|
||||||
.field("console_address", &self.console_address)
|
|
||||||
.field("obs_endpoint", &self.obs_endpoint)
|
|
||||||
.field("tls_path", &self.tls_path)
|
|
||||||
.field("license", &rustfs_credentials::Masked(self.license.as_deref()))
|
|
||||||
.field("region", &self.region)
|
|
||||||
.field("kms_enable", &self.kms_enable)
|
|
||||||
.field("kms_backend", &self.kms_backend)
|
|
||||||
.field("kms_key_dir", &self.kms_key_dir)
|
|
||||||
.field("kms_vault_address", &self.kms_vault_address)
|
|
||||||
.field("kms_vault_token", &rustfs_credentials::Masked(self.kms_vault_token.as_deref()))
|
|
||||||
.field("kms_default_key_id", &self.kms_default_key_id)
|
|
||||||
.field("buffer_profile_disable", &self.buffer_profile_disable)
|
|
||||||
.field("buffer_profile", &self.buffer_profile)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Parsed server options.
|
||||||
|
//!
|
||||||
|
//! This module contains the `Opt` struct which holds parsed server options
|
||||||
|
//! and methods for parsing command line arguments.
|
||||||
|
|
||||||
|
use super::Config;
|
||||||
|
use super::cli::{Cli, CommandResult, Commands, ServerOpts, default_server_opts, preprocess_args_for_legacy};
|
||||||
|
use crate::apply_external_env_compat;
|
||||||
|
use CommandResult::Server;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Parsed server options. Public for tests and backward compatibility.
|
||||||
|
/// Use `Opt::parse_from` or `Config::parse()` to obtain.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Opt {
|
||||||
|
pub volumes: Vec<String>,
|
||||||
|
pub address: String,
|
||||||
|
pub server_domains: Vec<String>,
|
||||||
|
pub access_key: Option<String>,
|
||||||
|
pub access_key_file: Option<PathBuf>,
|
||||||
|
pub secret_key: Option<String>,
|
||||||
|
pub secret_key_file: Option<PathBuf>,
|
||||||
|
pub console_enable: bool,
|
||||||
|
pub console_address: String,
|
||||||
|
pub obs_endpoint: String,
|
||||||
|
pub tls_path: Option<String>,
|
||||||
|
pub license: Option<String>,
|
||||||
|
pub region: Option<String>,
|
||||||
|
pub kms_enable: bool,
|
||||||
|
pub kms_backend: String,
|
||||||
|
pub kms_key_dir: Option<String>,
|
||||||
|
pub kms_vault_address: Option<String>,
|
||||||
|
pub kms_vault_token: Option<String>,
|
||||||
|
pub kms_default_key_id: Option<String>,
|
||||||
|
pub buffer_profile_disable: bool,
|
||||||
|
pub buffer_profile: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opt {
|
||||||
|
/// Create Opt from ServerOpts
|
||||||
|
pub(super) fn from_server_opts(o: ServerOpts) -> Self {
|
||||||
|
Self {
|
||||||
|
volumes: o.volumes,
|
||||||
|
address: o.address,
|
||||||
|
server_domains: o.server_domains,
|
||||||
|
access_key: o.access_key,
|
||||||
|
access_key_file: o.access_key_file,
|
||||||
|
secret_key: o.secret_key,
|
||||||
|
secret_key_file: o.secret_key_file,
|
||||||
|
console_enable: o.console_enable,
|
||||||
|
console_address: o.console_address,
|
||||||
|
obs_endpoint: o.obs_endpoint,
|
||||||
|
tls_path: o.tls_path,
|
||||||
|
license: o.license,
|
||||||
|
region: o.region,
|
||||||
|
kms_enable: o.kms_enable,
|
||||||
|
kms_backend: o.kms_backend,
|
||||||
|
kms_key_dir: o.kms_key_dir,
|
||||||
|
kms_vault_address: o.kms_vault_address,
|
||||||
|
kms_vault_token: o.kms_vault_token,
|
||||||
|
kms_default_key_id: o.kms_default_key_id,
|
||||||
|
buffer_profile_disable: o.buffer_profile_disable,
|
||||||
|
buffer_profile: o.buffer_profile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from preprocessed args. Supports both `rustfs <volume>` and `rustfs server <volume>`.
|
||||||
|
#[allow(dead_code)] // used in config_test
|
||||||
|
pub fn parse_from<I, T>(args: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<std::ffi::OsString> + Clone,
|
||||||
|
{
|
||||||
|
let _ = apply_external_env_compat();
|
||||||
|
let args: Vec<String> = args.into_iter().map(|a| a.into().to_string_lossy().into_owned()).collect();
|
||||||
|
let args = preprocess_args_for_legacy(args);
|
||||||
|
let cli = Cli::parse_from(args);
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::Server(opts)) => Self::from_server_opts(*opts),
|
||||||
|
Some(Commands::Info(_)) => {
|
||||||
|
// This should not happen in parse_from, as it's handled by parse_command
|
||||||
|
panic!("Info command should be handled by parse_command");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Default to server with empty volumes (will be filled from env)
|
||||||
|
Self::from_server_opts(default_server_opts())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from preprocessed args and return the command type.
|
||||||
|
/// Returns Ok(Info(opts)) if info command, Ok(Server(opts)) if server command.
|
||||||
|
pub fn parse_command<I, T>(args: I) -> Result<CommandResult, clap::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<std::ffi::OsString> + Clone,
|
||||||
|
{
|
||||||
|
let _ = apply_external_env_compat();
|
||||||
|
let args: Vec<String> = args.into_iter().map(|a| a.into().to_string_lossy().into_owned()).collect();
|
||||||
|
let args = preprocess_args_for_legacy(args);
|
||||||
|
let cli = Cli::try_parse_from(args)?;
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::Info(opts)) => Ok(CommandResult::Info(opts)),
|
||||||
|
Some(Commands::Server(opts)) => Self::server_command_result(Self::from_server_opts(*opts)),
|
||||||
|
None => {
|
||||||
|
// Default to server with empty volumes (will be filled from env)
|
||||||
|
Self::server_command_result(Self::from_server_opts(default_server_opts()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to convert Opt to CommandResult::Server with error handling
|
||||||
|
fn server_command_result(opt: Opt) -> Result<CommandResult, clap::Error> {
|
||||||
|
Ok(Server(Box::new(Config::from_opt(opt).map_err(|e| {
|
||||||
|
clap::Error::raw(clap::error::ErrorKind::ValueValidation, e.to_string())
|
||||||
|
})?)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try parse from args, returns error on invalid input.
|
||||||
|
#[allow(dead_code)] // used in config_test
|
||||||
|
pub fn try_parse_from<I, T>(args: I) -> Result<Self, clap::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<std::ffi::OsString> + Clone,
|
||||||
|
{
|
||||||
|
let _ = apply_external_env_compat();
|
||||||
|
let args: Vec<String> = args.into_iter().map(|a| a.into().to_string_lossy().into_owned()).collect();
|
||||||
|
let args = preprocess_args_for_legacy(args);
|
||||||
|
let cli = Cli::try_parse_from(args)?;
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::Server(opts)) => Ok(Self::from_server_opts(*opts)),
|
||||||
|
Some(Commands::Info(_)) => Err(clap::Error::new(clap::error::ErrorKind::DisplayHelp)),
|
||||||
|
None => {
|
||||||
|
// Default to server with empty volumes
|
||||||
|
Ok(Self::from_server_opts(default_server_opts()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from env::args(). Used by Config::parse().
|
||||||
|
#[allow(dead_code)] // used in config_test
|
||||||
|
fn parse() -> Self {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
Self::parse_from(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Configuration snapshot for info command.
|
||||||
|
//!
|
||||||
|
//! This module provides a lightweight snapshot of configuration values
|
||||||
|
//! that can be accessed globally without needing the full Config struct.
|
||||||
|
|
||||||
|
use super::Config;
|
||||||
|
use crate::config::config_struct::resolve_credential;
|
||||||
|
use rustfs_config::{
|
||||||
|
DEFAULT_ADDRESS, DEFAULT_BUFFER_PROFILE, DEFAULT_CONSOLE_ADDRESS, DEFAULT_CONSOLE_ENABLE, DEFAULT_KMS_BACKEND,
|
||||||
|
DEFAULT_KMS_ENABLE, DEFAULT_OBS_ENDPOINT, ENV_RUSTFS_ACCESS_KEY, ENV_RUSTFS_ACCESS_KEY_FILE, ENV_RUSTFS_ADDRESS,
|
||||||
|
ENV_RUSTFS_BUFFER_PROFILE, ENV_RUSTFS_CONSOLE_ADDRESS, ENV_RUSTFS_CONSOLE_ENABLE, ENV_RUSTFS_KMS_BACKEND,
|
||||||
|
ENV_RUSTFS_KMS_ENABLE, ENV_RUSTFS_OBS_ENDPOINT, ENV_RUSTFS_REGION, ENV_RUSTFS_ROOT_USER, ENV_RUSTFS_TLS_PATH, RUSTFS_REGION,
|
||||||
|
};
|
||||||
|
use rustfs_credentials::DEFAULT_ACCESS_KEY;
|
||||||
|
use rustfs_utils::{get_env_bool, get_env_opt_str, get_env_str};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
/// Fallback snapshot used only for display when the global snapshot
|
||||||
|
/// has not yet been initialized (e.g., for the `--info` command).
|
||||||
|
/// This avoids leaking memory while still providing a `'static` reference.
|
||||||
|
static DISPLAY_CONFIG_SNAPSHOT: OnceLock<ConfigSnapshot> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Configuration snapshot for info command display.
|
||||||
|
/// This stores key configuration values that can be accessed without
|
||||||
|
/// needing the full Config struct.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ConfigSnapshot {
|
||||||
|
/// Server bind address
|
||||||
|
pub address: String,
|
||||||
|
/// Console server enabled
|
||||||
|
pub console_enable: bool,
|
||||||
|
/// Console server address
|
||||||
|
pub console_address: String,
|
||||||
|
/// Server region
|
||||||
|
pub region: Option<String>,
|
||||||
|
/// Access key (for display, should be masked)
|
||||||
|
pub access_key: String,
|
||||||
|
/// OBS endpoint
|
||||||
|
pub obs_endpoint: String,
|
||||||
|
/// TLS path
|
||||||
|
pub tls_path: Option<String>,
|
||||||
|
/// KMS enabled
|
||||||
|
pub kms_enable: bool,
|
||||||
|
/// KMS backend type
|
||||||
|
pub kms_backend: String,
|
||||||
|
/// Buffer profile
|
||||||
|
pub buffer_profile: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigSnapshot {
|
||||||
|
/// Create a snapshot from Config
|
||||||
|
pub fn from_config(config: &Config) -> Self {
|
||||||
|
Self {
|
||||||
|
address: config.address.clone(),
|
||||||
|
console_enable: config.console_enable,
|
||||||
|
console_address: config.console_address.clone(),
|
||||||
|
region: config.region.clone(),
|
||||||
|
access_key: config.access_key.clone(),
|
||||||
|
obs_endpoint: config.obs_endpoint.clone(),
|
||||||
|
tls_path: config.tls_path.clone(),
|
||||||
|
kms_enable: config.kms_enable,
|
||||||
|
kms_backend: config.kms_backend.clone(),
|
||||||
|
buffer_profile: config.buffer_profile.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a default snapshot from environment variables and defaults
|
||||||
|
pub fn from_env() -> Self {
|
||||||
|
let access_key = resolve_credential(
|
||||||
|
get_env_opt_str(ENV_RUSTFS_ACCESS_KEY),
|
||||||
|
get_env_opt_str(ENV_RUSTFS_ACCESS_KEY_FILE),
|
||||||
|
ENV_RUSTFS_ROOT_USER,
|
||||||
|
DEFAULT_ACCESS_KEY,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|_| DEFAULT_ACCESS_KEY.to_string());
|
||||||
|
Self {
|
||||||
|
address: get_env_str(ENV_RUSTFS_ADDRESS, DEFAULT_ADDRESS),
|
||||||
|
console_enable: get_env_bool(ENV_RUSTFS_CONSOLE_ENABLE, DEFAULT_CONSOLE_ENABLE),
|
||||||
|
console_address: get_env_str(ENV_RUSTFS_CONSOLE_ADDRESS, DEFAULT_CONSOLE_ADDRESS),
|
||||||
|
region: Some(get_env_str(ENV_RUSTFS_REGION, RUSTFS_REGION)),
|
||||||
|
access_key,
|
||||||
|
obs_endpoint: get_env_str(ENV_RUSTFS_OBS_ENDPOINT, DEFAULT_OBS_ENDPOINT),
|
||||||
|
tls_path: get_env_opt_str(ENV_RUSTFS_TLS_PATH),
|
||||||
|
kms_enable: get_env_bool(ENV_RUSTFS_KMS_ENABLE, DEFAULT_KMS_ENABLE),
|
||||||
|
kms_backend: get_env_str(ENV_RUSTFS_KMS_BACKEND, DEFAULT_KMS_BACKEND),
|
||||||
|
buffer_profile: get_env_str(ENV_RUSTFS_BUFFER_PROFILE, DEFAULT_BUFFER_PROFILE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global configuration snapshot storage
|
||||||
|
static GLOBAL_CONFIG_SNAPSHOT: OnceLock<ConfigSnapshot> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Initialize the global config snapshot from a Config instance.
|
||||||
|
/// This should be called once during server startup.
|
||||||
|
///
|
||||||
|
/// This is the ONLY function that can set the global snapshot.
|
||||||
|
/// Once set, it cannot be changed.
|
||||||
|
pub fn init_config_snapshot(config: &Config) {
|
||||||
|
let snapshot = ConfigSnapshot::from_config(config);
|
||||||
|
if GLOBAL_CONFIG_SNAPSHOT.set(snapshot).is_err() {
|
||||||
|
// Already initialized, log a warning
|
||||||
|
tracing::warn!("Config snapshot already initialized, ignoring re-initialization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the global config snapshot if initialized.
|
||||||
|
/// Returns None if not initialized (e.g., when running --info before server starts).
|
||||||
|
#[allow(dead_code)] // used in info command
|
||||||
|
pub fn get_config_snapshot() -> Option<&'static ConfigSnapshot> {
|
||||||
|
GLOBAL_CONFIG_SNAPSHOT.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the global config snapshot has been initialized.
|
||||||
|
#[allow(dead_code)] // may be used for debugging
|
||||||
|
pub fn is_config_snapshot_initialized() -> bool {
|
||||||
|
GLOBAL_CONFIG_SNAPSHOT.get().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get config snapshot for display purposes (backward-compatible wrapper).
|
||||||
|
///
|
||||||
|
/// - If the global snapshot is initialized (server has started), returns a reference to it.
|
||||||
|
/// - If not initialized (e.g., --info command before server starts), returns a temporary
|
||||||
|
/// snapshot created from environment variables WITHOUT updating the global storage.
|
||||||
|
///
|
||||||
|
/// Despite its name, this function no longer initializes the global snapshot; it simply
|
||||||
|
/// delegates to `get_config_snapshot_for_display` to ensure the global snapshot is ONLY set
|
||||||
|
/// by `init_config_snapshot` during server startup.
|
||||||
|
#[allow(dead_code)] // kept for backward compatibility
|
||||||
|
pub fn get_or_init_config_snapshot() -> &'static ConfigSnapshot {
|
||||||
|
get_config_snapshot_for_display()
|
||||||
|
}
|
||||||
|
/// Get config snapshot for display, without modifying global state.
|
||||||
|
///
|
||||||
|
/// This function is used by the --info command to display configuration:
|
||||||
|
/// - Returns the global snapshot if initialized
|
||||||
|
/// - Otherwise creates a temporary snapshot from environment variables (does NOT store it)
|
||||||
|
///
|
||||||
|
/// Note: This returns a static reference for API compatibility. When the global snapshot
|
||||||
|
/// is not initialized, it creates a leaked Box to provide a static lifetime.
|
||||||
|
/// This is safe because it's only used for read-only display purposes.
|
||||||
|
pub fn get_config_snapshot_for_display() -> &'static ConfigSnapshot {
|
||||||
|
if let Some(snapshot) = GLOBAL_CONFIG_SNAPSHOT.get() {
|
||||||
|
snapshot
|
||||||
|
} else {
|
||||||
|
// Not initialized - create from env without storing in the global snapshot.
|
||||||
|
// Use a dedicated OnceLock to cache a single display snapshot without leaking.
|
||||||
|
DISPLAY_CONFIG_SNAPSHOT.get_or_init(ConfigSnapshot::from_env)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,9 +69,10 @@ pub fn get_global_buffer_config() -> &'static RustFSBufferConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Workload profile types that define buffer sizing strategies
|
/// Workload profile types that define buffer sizing strategies
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
pub enum WorkloadProfile {
|
pub enum WorkloadProfile {
|
||||||
/// General purpose - default configuration with balanced performance and memory
|
/// General purpose - default configuration with balanced performance and memory
|
||||||
|
#[default]
|
||||||
GeneralPurpose,
|
GeneralPurpose,
|
||||||
/// AI/ML training: optimized for large sequential reads with maximum throughput
|
/// AI/ML training: optimized for large sequential reads with maximum throughput
|
||||||
AiTraining,
|
AiTraining,
|
||||||
@@ -84,7 +85,6 @@ pub enum WorkloadProfile {
|
|||||||
/// Secure storage: security first, memory constrained for compliance
|
/// Secure storage: security first, memory constrained for compliance
|
||||||
SecureStorage,
|
SecureStorage,
|
||||||
/// Custom configuration for specialized requirements
|
/// Custom configuration for specialized requirements
|
||||||
#[allow(dead_code)]
|
|
||||||
Custom(BufferConfig),
|
Custom(BufferConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,6 @@ pub struct BufferConfig {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RustFSBufferConfig {
|
pub struct RustFSBufferConfig {
|
||||||
/// Selected workload profile
|
/// Selected workload profile
|
||||||
#[allow(dead_code)]
|
|
||||||
pub workload: WorkloadProfile,
|
pub workload: WorkloadProfile,
|
||||||
/// Computed buffer configuration (either from profile or custom)
|
/// Computed buffer configuration (either from profile or custom)
|
||||||
pub base_config: BufferConfig,
|
pub base_config: BufferConfig,
|
||||||
@@ -144,6 +143,25 @@ impl WorkloadProfile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a custom workload profile with specified buffer configuration
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `min_size` - Minimum buffer size in bytes
|
||||||
|
/// * `max_size` - Maximum buffer size in bytes
|
||||||
|
/// * `default_unknown` - Default size for unknown file size scenarios
|
||||||
|
/// * `thresholds` - File size thresholds and corresponding buffer sizes
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A WorkloadProfile::Custom with the specified configuration
|
||||||
|
pub fn custom(min_size: usize, max_size: usize, default_unknown: usize, thresholds: Vec<(i64, usize)>) -> Self {
|
||||||
|
WorkloadProfile::Custom(BufferConfig {
|
||||||
|
min_size,
|
||||||
|
max_size,
|
||||||
|
default_unknown,
|
||||||
|
thresholds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the buffer configuration for this workload profile
|
/// Get the buffer configuration for this workload profile
|
||||||
pub fn config(&self) -> BufferConfig {
|
pub fn config(&self) -> BufferConfig {
|
||||||
match self {
|
match self {
|
||||||
@@ -309,7 +327,6 @@ impl BufferConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Validate the buffer configuration
|
/// Validate the buffer configuration
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn validate(&self) -> Result<(), String> {
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
if self.min_size == 0 {
|
if self.min_size == 0 {
|
||||||
return Err("min_size must be greater than 0".to_string());
|
return Err("min_size must be greater than 0".to_string());
|
||||||
@@ -361,6 +378,29 @@ impl RustFSBufferConfig {
|
|||||||
pub fn get_buffer_size(&self, file_size: i64) -> usize {
|
pub fn get_buffer_size(&self, file_size: i64) -> usize {
|
||||||
self.base_config.calculate_buffer_size(file_size)
|
self.base_config.calculate_buffer_size(file_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current workload profile
|
||||||
|
pub fn workload_profile(&self) -> &WorkloadProfile {
|
||||||
|
&self.workload
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of the current workload profile
|
||||||
|
pub fn workload_name(&self) -> String {
|
||||||
|
match &self.workload {
|
||||||
|
WorkloadProfile::GeneralPurpose => "GeneralPurpose".to_string(),
|
||||||
|
WorkloadProfile::AiTraining => "AiTraining".to_string(),
|
||||||
|
WorkloadProfile::DataAnalytics => "DataAnalytics".to_string(),
|
||||||
|
WorkloadProfile::WebWorkload => "WebWorkload".to_string(),
|
||||||
|
WorkloadProfile::IndustrialIoT => "IndustrialIoT".to_string(),
|
||||||
|
WorkloadProfile::SecureStorage => "SecureStorage".to_string(),
|
||||||
|
WorkloadProfile::Custom(_) => "Custom".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate the buffer configuration
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
self.base_config.validate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RustFSBufferConfig {
|
impl Default for RustFSBufferConfig {
|
||||||
@@ -616,6 +656,41 @@ mod tests {
|
|||||||
assert_eq!(WorkloadProfile::from_name(""), WorkloadProfile::GeneralPurpose);
|
assert_eq!(WorkloadProfile::from_name(""), WorkloadProfile::GeneralPurpose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_workload_profile() {
|
||||||
|
// Create a custom profile with specific buffer sizes
|
||||||
|
let custom_profile = WorkloadProfile::custom(
|
||||||
|
32 * KI_B, // min_size: 32KB
|
||||||
|
2 * MI_B, // max_size: 2MB
|
||||||
|
256 * KI_B, // default_unknown: 256KB
|
||||||
|
vec![
|
||||||
|
(MI_B as i64, 64 * KI_B), // < 1MB: 64KB
|
||||||
|
(10 * MI_B as i64, 128 * KI_B), // 1MB-10MB: 128KB
|
||||||
|
(i64::MAX, 512 * KI_B), // >= 10MB: 512KB
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify it's a Custom variant
|
||||||
|
match &custom_profile {
|
||||||
|
WorkloadProfile::Custom(config) => {
|
||||||
|
assert_eq!(config.min_size, 32 * KI_B);
|
||||||
|
assert_eq!(config.max_size, 2 * MI_B);
|
||||||
|
assert_eq!(config.default_unknown, 256 * KI_B);
|
||||||
|
assert_eq!(config.thresholds.len(), 3);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Custom variant"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test buffer size calculation with custom profile
|
||||||
|
let buffer_config = RustFSBufferConfig::new(custom_profile);
|
||||||
|
assert_eq!(buffer_config.get_buffer_size(500 * KI_B as i64), 64 * KI_B);
|
||||||
|
assert_eq!(buffer_config.get_buffer_size(5 * MI_B as i64), 128 * KI_B);
|
||||||
|
assert_eq!(buffer_config.get_buffer_size(100 * MI_B as i64), 512 * KI_B);
|
||||||
|
|
||||||
|
// Test validation
|
||||||
|
assert!(buffer_config.validate().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_global_buffer_config() {
|
fn test_global_buffer_config() {
|
||||||
use super::{is_buffer_profile_enabled, set_buffer_profile_enabled};
|
use super::{is_buffer_profile_enabled, set_buffer_profile_enabled};
|
||||||
|
|||||||
+55
-6
@@ -14,10 +14,14 @@
|
|||||||
|
|
||||||
use crate::storage::{process_lambda_configurations, process_queue_configurations, process_topic_configurations};
|
use crate::storage::{process_lambda_configurations, process_queue_configurations, process_topic_configurations};
|
||||||
use crate::{admin, config, version};
|
use crate::{admin, config, version};
|
||||||
use rustfs_config::{DEFAULT_UPDATE_CHECK, ENV_UPDATE_CHECK, RUSTFS_REGION};
|
use rustfs_config::{
|
||||||
|
DEFAULT_BUFFER_MAX_SIZE, DEFAULT_BUFFER_MIN_SIZE, DEFAULT_BUFFER_PROFILE, DEFAULT_BUFFER_UNKNOWN_SIZE, DEFAULT_UPDATE_CHECK,
|
||||||
|
ENV_RUSTFS_BUFFER_DEFAULT_SIZE, ENV_RUSTFS_BUFFER_MAX_SIZE, ENV_RUSTFS_BUFFER_MIN_SIZE, ENV_UPDATE_CHECK, RUSTFS_REGION,
|
||||||
|
};
|
||||||
use rustfs_ecstore::bucket::metadata_sys;
|
use rustfs_ecstore::bucket::metadata_sys;
|
||||||
use rustfs_notify::notifier_global;
|
use rustfs_notify::notifier_global;
|
||||||
use rustfs_targets::arn::{ARN, TargetIDError};
|
use rustfs_targets::arn::{ARN, TargetIDError};
|
||||||
|
use rustfs_utils::get_env_usize;
|
||||||
use s3s::s3_error;
|
use s3s::s3_error;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
@@ -297,10 +301,9 @@ pub(crate) async fn init_kms_system(config: &config::Config) -> std::io::Result<
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `config` - The application configuration options
|
/// * `config` - The application configuration options
|
||||||
pub(crate) fn init_buffer_profile_system(config: &config::Config) {
|
pub(crate) fn init_buffer_profile_system(config: &config::Config) {
|
||||||
use crate::config::workload_profiles::{
|
use crate::config::{RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled};
|
||||||
RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Whether buffer profiling is disabled or not, it is enabled by default, unless the user explicitly sets '--buffer-profile-disable' or 'RUSTFS_BUFFER_PROFILE_DISABLE=true'
|
||||||
if config.buffer_profile_disable {
|
if config.buffer_profile_disable {
|
||||||
// User explicitly disabled buffer profiling - use GeneralPurpose profile in disabled mode
|
// User explicitly disabled buffer profiling - use GeneralPurpose profile in disabled mode
|
||||||
info!("Buffer profiling disabled via --buffer-profile-disable, using GeneralPurpose profile");
|
info!("Buffer profiling disabled via --buffer-profile-disable, using GeneralPurpose profile");
|
||||||
@@ -310,13 +313,59 @@ pub(crate) fn init_buffer_profile_system(config: &config::Config) {
|
|||||||
info!("Buffer profiling enabled with profile: {}", config.buffer_profile);
|
info!("Buffer profiling enabled with profile: {}", config.buffer_profile);
|
||||||
|
|
||||||
// Parse the workload profile from configuration string
|
// Parse the workload profile from configuration string
|
||||||
let profile = WorkloadProfile::from_name(&config.buffer_profile);
|
// Support a custom profile when buffer_profile is set to "custom";
|
||||||
|
// its sizes are controlled via RUSTFS_BUFFER_MIN_SIZE, RUSTFS_BUFFER_MAX_SIZE,
|
||||||
|
// and RUSTFS_BUFFER_DEFAULT_SIZE environment variables.
|
||||||
|
let profile = if config.buffer_profile.eq_ignore_ascii_case("custom") {
|
||||||
|
// Try to create custom profile from environment variables
|
||||||
|
let min_size = get_env_usize(ENV_RUSTFS_BUFFER_MIN_SIZE, DEFAULT_BUFFER_MIN_SIZE);
|
||||||
|
let max_size = get_env_usize(ENV_RUSTFS_BUFFER_MAX_SIZE, DEFAULT_BUFFER_MAX_SIZE);
|
||||||
|
let default_unknown = get_env_usize(ENV_RUSTFS_BUFFER_DEFAULT_SIZE, DEFAULT_BUFFER_UNKNOWN_SIZE);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Creating custom buffer profile: min={}, max={}, default={}",
|
||||||
|
min_size, max_size, default_unknown
|
||||||
|
);
|
||||||
|
WorkloadProfile::custom(
|
||||||
|
min_size,
|
||||||
|
max_size,
|
||||||
|
default_unknown,
|
||||||
|
vec![
|
||||||
|
(1024 * 1024, 64 * 1024), // < 1MB: 64KB
|
||||||
|
(100 * 1024 * 1024, 256 * 1024), // 1MB-100MB: 256KB
|
||||||
|
(i64::MAX, 1024 * 1024), // >= 100MB: 1MB
|
||||||
|
],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
WorkloadProfile::from_name(&config.buffer_profile)
|
||||||
|
};
|
||||||
|
|
||||||
// Log the selected profile for operational visibility
|
// Log the selected profile for operational visibility
|
||||||
info!("Active buffer profile: {:?}", profile);
|
info!("Active buffer profile: {:?}", profile);
|
||||||
|
|
||||||
|
// Create and validate buffer configuration
|
||||||
|
let mut buffer_config = RustFSBufferConfig::new(profile);
|
||||||
|
if let Err(e) = buffer_config.validate() {
|
||||||
|
warn!("Buffer configuration validation failed: {}. Falling back to GeneralPurpose profile.", e);
|
||||||
|
// Fall back to a known-good profile to avoid installing an invalid configuration
|
||||||
|
let fallback_profile = WorkloadProfile::from_name(DEFAULT_BUFFER_PROFILE);
|
||||||
|
info!("Using fallback buffer profile: {:?}", fallback_profile);
|
||||||
|
let fallback_config = RustFSBufferConfig::new(fallback_profile);
|
||||||
|
if let Err(e2) = fallback_config.validate() {
|
||||||
|
error!(
|
||||||
|
"Fallback buffer configuration validation failed: {}. Aborting buffer profiling initialization.",
|
||||||
|
e2
|
||||||
|
);
|
||||||
|
panic!("Failed to initialize a valid RustFS buffer configuration");
|
||||||
|
}
|
||||||
|
buffer_config = fallback_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the workload profile name
|
||||||
|
info!("Workload profile: {}", buffer_config.workload_name());
|
||||||
|
|
||||||
// Initialize the global buffer configuration
|
// Initialize the global buffer configuration
|
||||||
init_global_buffer_config(RustFSBufferConfig::new(profile));
|
init_global_buffer_config(buffer_config);
|
||||||
|
|
||||||
// Enable buffer profiling globally
|
// Enable buffer profiling globally
|
||||||
set_buffer_profile_enabled(true);
|
set_buffer_profile_enabled(true);
|
||||||
|
|||||||
+25
-3
@@ -151,8 +151,30 @@ fn format_external_prefix_mappings(report: &ExternalEnvCompatReport) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn async_main() -> Result<()> {
|
async fn async_main() -> Result<()> {
|
||||||
// Parse the obtained parameters
|
// Parse command line arguments
|
||||||
let config = config::Config::parse()?;
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let command_result = match config::Opt::parse_command(args) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Command parse failed, error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle info command
|
||||||
|
if let config::CommandResult::Info(opts) = command_result {
|
||||||
|
config::execute_info(&opts);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get config for server command
|
||||||
|
let config = match command_result {
|
||||||
|
config::CommandResult::Server(cfg) => cfg,
|
||||||
|
config::CommandResult::Info(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize the global config snapshot for info command
|
||||||
|
config::init_config_snapshot(&config);
|
||||||
|
|
||||||
// Initialize the configuration
|
// Initialize the configuration
|
||||||
init_license(config.license.clone());
|
init_license(config.license.clone());
|
||||||
@@ -211,7 +233,7 @@ async fn async_main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run parameters
|
// Run parameters
|
||||||
match run(config).await {
|
match run(*config).await {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Server encountered an error and is shutting down: {}", e);
|
error!("Server encountered an error and is shutting down: {}", e);
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::config::workload_profiles::{
|
use crate::config::{RustFSBufferConfig, WorkloadProfile, get_global_buffer_config, is_buffer_profile_enabled};
|
||||||
RustFSBufferConfig, WorkloadProfile, get_global_buffer_config, is_buffer_profile_enabled,
|
|
||||||
};
|
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use crate::server::cors;
|
use crate::server::cors;
|
||||||
use crate::storage::ecfs::ListObjectUnorderedQuery;
|
use crate::storage::ecfs::ListObjectUnorderedQuery;
|
||||||
@@ -119,6 +117,22 @@ pub(crate) fn apply_lock_retention(object_lock_config: Option<ObjectLockConfigur
|
|||||||
/// 10 * 1024 * 1024,
|
/// 10 * 1024 * 1024,
|
||||||
/// Some(WorkloadProfile::SecureStorage)
|
/// Some(WorkloadProfile::SecureStorage)
|
||||||
/// );
|
/// );
|
||||||
|
///
|
||||||
|
/// // Use custom profile for specialized requirements
|
||||||
|
/// let custom_profile = WorkloadProfile::custom(
|
||||||
|
/// 32 * 1024, // min_size: 32KB
|
||||||
|
/// 2 * 1024 * 1024, // max_size: 2MB
|
||||||
|
/// 256 * 1024, // default_unknown: 256KB
|
||||||
|
/// vec![
|
||||||
|
/// (1024 * 1024, 64 * 1024), // < 1MB: 64KB
|
||||||
|
/// (10 * 1024 * 1024, 128 * 1024), // 1MB-10MB: 128KB
|
||||||
|
/// (i64::MAX, 512 * 1024), // >= 10MB: 512KB
|
||||||
|
/// ],
|
||||||
|
/// );
|
||||||
|
/// let buffer_size = get_adaptive_buffer_size_with_profile(
|
||||||
|
/// 5 * 1024 * 1024,
|
||||||
|
/// Some(custom_profile)
|
||||||
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::config::workload_profiles::WorkloadProfile;
|
use crate::config::WorkloadProfile;
|
||||||
use crate::server::cors;
|
use crate::server::cors;
|
||||||
use crate::storage::ecfs::FS;
|
use crate::storage::ecfs::FS;
|
||||||
use crate::storage::s3_api::common::{rustfs_initiator, rustfs_owner};
|
use crate::storage::s3_api::common::{rustfs_initiator, rustfs_owner};
|
||||||
@@ -281,9 +281,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_phase3_default_behavior() {
|
fn test_phase3_default_behavior() {
|
||||||
use crate::config::workload_profiles::{
|
use crate::config::{RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled};
|
||||||
RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
const KB: i64 = 1024;
|
const KB: i64 = 1024;
|
||||||
const MB: i64 = 1024 * 1024;
|
const MB: i64 = 1024 * 1024;
|
||||||
@@ -304,7 +302,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_buffer_size_opt_in() {
|
fn test_buffer_size_opt_in() {
|
||||||
use crate::config::workload_profiles::{is_buffer_profile_enabled, set_buffer_profile_enabled};
|
use crate::config::{is_buffer_profile_enabled, set_buffer_profile_enabled};
|
||||||
|
|
||||||
const KB: i64 = 1024;
|
const KB: i64 = 1024;
|
||||||
const MB: i64 = 1024 * 1024;
|
const MB: i64 = 1024 * 1024;
|
||||||
@@ -336,9 +334,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_phase4_full_integration() {
|
fn test_phase4_full_integration() {
|
||||||
use crate::config::workload_profiles::{
|
use crate::config::{RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled};
|
||||||
RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
const KB: i64 = 1024;
|
const KB: i64 = 1024;
|
||||||
const MB: i64 = 1024 * 1024;
|
const MB: i64 = 1024 * 1024;
|
||||||
|
|||||||
Reference in New Issue
Block a user