Skip to content

Commit e1b93f2

Browse files
committed
[slayerfs]: Improve overall demo functionality
Signed-off-by: Luxian <[email protected]>
1 parent 425bc13 commit e1b93f2

File tree

9 files changed

+320
-13
lines changed

9 files changed

+320
-13
lines changed

project/Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

project/slayerfs/.env

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# AWS S3 Configuration
2+
AWS_ACCESS_KEY_ID=rustfsadmin
3+
AWS_SECRET_ACCESS_KEY=rustfsadmin
4+
AWS_REGION=us-east-1
5+
AWS_DEFAULT_REGION=us-east-1
6+
AWS_ALLOW_HTTP=true
7+
AWS_EC2_METADATA_DISABLED=true
8+
9+
# S3 Endpoint Configuration
10+
S3_ENDPOINT=http://127.0.0.1:9000
11+
S3_BUCKET=slayerfs
12+
S3_FORCE_PATH_STYLE=true
13+
14+
# Note: Before running the demo, make sure to:
15+
# 1. Start MinIO server: docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"
16+
# 2. Create bucket using MinIO client or web console at http://localhost:9001
17+
# 3. Or use AWS CLI: aws --endpoint-url http://localhost:9000 s3 mb s3://slayerfs

project/slayerfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
slayerfs-meta/

project/slayerfs/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ aws-config = { version = "1" }
1313
aws-sdk-s3 = { version = "1", features = ["behavior-version-latest"] }
1414
base64 = "0.21"
1515
md5 = "0.7"
16+
http = "1"
1617
futures = "0.3"
1718
rfuse3 = { version = "0.0.3", features = ["tokio-runtime","unprivileged"]}
1819
futures-util = "0.3.31"
@@ -37,6 +38,7 @@ bitflags = "1.3"
3738
auto_impl = "1.3.0"
3839
tracing = "0.1.41"
3940
tracing-subscriber = "0.3.20"
41+
dotenv = "0.15"
4042

4143
[dev-dependencies]
4244
tempfile = "3"

project/slayerfs/dep

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fuse3
2+
protobuf-compiler
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
use clap::Parser;
2+
use slayerfs::cadapter::client::ObjectClient;
3+
use slayerfs::cadapter::s3::{S3Backend, S3Config};
4+
use slayerfs::chuck::chunk::ChunkLayout;
5+
use slayerfs::chuck::store::ObjectBlockStore;
6+
use slayerfs::fuse::mount::mount_vfs_unprivileged;
7+
use slayerfs::vfs::fs::VFS;
8+
use std::path::PathBuf;
9+
use tokio::signal;
10+
use dotenv;
11+
12+
#[derive(Parser)]
13+
#[command(author, version, about, long_about = None)]
14+
struct Args {
15+
/// Configuration file path (e.g. slayerfs-sqlite.yml, slayerfs-etcd.yml)
16+
#[arg(short, long, default_value = "./slayerfs-sqlite.yml")]
17+
config: PathBuf,
18+
19+
/// Mount point path
20+
#[arg(short, long, default_value = "/tmp/mount")]
21+
mount: PathBuf,
22+
23+
/// Directory used to persist metadata artifacts (SQLite database, config, etc.)
24+
#[arg(long, default_value = "./slayerfs-meta")]
25+
meta_dir: PathBuf,
26+
27+
/// Target S3 bucket name (can be overridden by S3_BUCKET env var)
28+
#[arg(long)]
29+
bucket: Option<String>,
30+
31+
/// S3-compatible endpoint URL (can be overridden by S3_ENDPOINT env var)
32+
#[arg(long)]
33+
endpoint: Option<String>,
34+
35+
/// Optional AWS region (can be overridden by AWS_REGION env var)
36+
#[arg(long)]
37+
region: Option<String>,
38+
}
39+
40+
/// Process config file and adjust SQLite path to absolute path
41+
fn process_config_for_backend(
42+
config_content: &str,
43+
meta_dir: &std::path::Path,
44+
) -> Result<String, Box<dyn std::error::Error>> {
45+
let config: serde_yaml::Value = serde_yaml::from_str(config_content)?;
46+
47+
if let Some(database) = config.get("database") {
48+
if let Some(db_type) = database.get("type").and_then(|t| t.as_str()) {
49+
match db_type {
50+
"sqlite" => {
51+
let db_path = meta_dir.join("metadata.db");
52+
let sqlite_url = format!("sqlite://{}?mode=rwc", db_path.display());
53+
54+
let processed_config = format!(
55+
r#"database:
56+
type: sqlite
57+
url: "{}"
58+
"#,
59+
sqlite_url
60+
);
61+
62+
println!("SQLite database path: {}", db_path.display());
63+
Ok(processed_config)
64+
}
65+
"postgres" | "etcd" => {
66+
println!("Using configured database backend: {}", db_type);
67+
Ok(config_content.to_string())
68+
}
69+
_ => Err(format!("Unsupported database type: {}", db_type).into()),
70+
}
71+
} else {
72+
Err("Missing database.type field in config file".into())
73+
}
74+
} else {
75+
Err("Missing database configuration in config file".into())
76+
}
77+
}
78+
79+
#[tokio::main]
80+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
81+
// 加载 .env 文件
82+
dotenv::dotenv().ok();
83+
84+
let format = tracing_subscriber::fmt::format().with_ansi(false);
85+
tracing_subscriber::fmt().event_format(format).init();
86+
87+
#[cfg(not(target_os = "linux"))]
88+
{
89+
eprintln!("This demo only works on Linux (requires FUSE support).");
90+
eprintln!("If you're on Windows, please run under WSL/WSL2 or Linux host.");
91+
std::process::exit(2);
92+
}
93+
94+
#[cfg(target_os = "linux")]
95+
{
96+
let args = Args::parse();
97+
98+
// 从环境变量获取配置,命令行参数优先
99+
let bucket = args.bucket
100+
.or_else(|| std::env::var("S3_BUCKET").ok())
101+
.ok_or("S3 bucket must be specified via --bucket or S3_BUCKET env var")?;
102+
103+
let endpoint = args.endpoint
104+
.or_else(|| std::env::var("S3_ENDPOINT").ok())
105+
.unwrap_or_else(|| "http://127.0.0.1:9000".to_string());
106+
107+
let region = args.region
108+
.or_else(|| std::env::var("AWS_REGION").ok());
109+
110+
println!("=== SlayerFS Persistence + S3 Demo ===");
111+
println!("Environment variables loaded from .env file");
112+
println!("Config file: {}", args.config.display());
113+
println!("Metadata dir: {}", args.meta_dir.display());
114+
println!("Mount point: {}", args.mount.display());
115+
println!("S3 bucket: {}", bucket);
116+
println!("S3 endpoint: {}", endpoint);
117+
if let Some(ref region) = region {
118+
println!("S3 region: {}", region);
119+
}
120+
println!();
121+
let config_file = args.config;
122+
let mount_point = args.mount;
123+
let meta_dir = args.meta_dir;
124+
125+
println!("=== SlayerFS Persistence + S3 Demo ===");
126+
println!("Environment variables loaded from .env file");
127+
println!("Config file: {}", config_file.display());
128+
println!("Metadata dir: {}", meta_dir.display());
129+
println!("Mount point: {}", mount_point.display());
130+
println!("S3 bucket: {}", bucket);
131+
println!("S3 endpoint: {}", endpoint);
132+
if let Some(ref region) = region {
133+
println!("S3 region: {}", region);
134+
}
135+
println!();
136+
137+
if !config_file.exists() {
138+
eprintln!(
139+
"Error: Config file {} does not exist",
140+
config_file.display()
141+
);
142+
std::process::exit(1);
143+
}
144+
145+
std::fs::create_dir_all(&mount_point).map_err(|e| {
146+
format!(
147+
"Cannot create mount point directory {}: {}",
148+
mount_point.display(),
149+
e
150+
)
151+
})?;
152+
std::fs::create_dir_all(&meta_dir).map_err(|e| {
153+
format!(
154+
"Cannot create metadata directory {}: {}",
155+
meta_dir.display(),
156+
e
157+
)
158+
})?;
159+
#[cfg(unix)]
160+
{
161+
use std::os::unix::fs::PermissionsExt;
162+
let permissions = std::fs::Permissions::from_mode(0o755);
163+
std::fs::set_permissions(&mount_point, permissions)?;
164+
}
165+
166+
if std::fs::metadata(&mount_point)
167+
.map(|m| !m.is_dir())
168+
.unwrap_or(false)
169+
{
170+
return Err(format!("Mount point {} is not a directory", mount_point.display()).into());
171+
}
172+
173+
if let Ok(entries) = std::fs::read_dir(&mount_point) {
174+
let count = entries.count();
175+
if count > 0 {
176+
eprintln!(
177+
"Warning: Mount point {} is not empty, may already be mounted",
178+
mount_point.display()
179+
);
180+
eprintln!("Please unmount first or use an empty directory");
181+
eprintln!("Try: fusermount -u {}", mount_point.display());
182+
return Err("Mount point not empty".into());
183+
}
184+
}
185+
186+
let config_content = std::fs::read_to_string(&config_file)
187+
.map_err(|e| format!("Cannot read config file: {}", e))?;
188+
189+
let meta_work_dir = meta_dir.join(".slayerfs");
190+
std::fs::create_dir_all(&meta_work_dir)?;
191+
192+
let target_config_path = meta_work_dir.join("slayerfs.yml");
193+
let processed_config = process_config_for_backend(&config_content, &meta_work_dir)?;
194+
std::fs::write(&target_config_path, processed_config)?;
195+
196+
// AWS 环境变量已从 .env 文件加载
197+
198+
let config = slayerfs::meta::config::Config::from_file(&target_config_path)
199+
.map_err(|e| format!("Failed to load config file: {}", e))?;
200+
let meta = slayerfs::meta::factory::MetaStoreFactory::create_from_config(config)
201+
.await
202+
.map_err(|e| format!("Failed to initialize metadata storage: {}", e))?;
203+
204+
let mut s3_config = S3Config {
205+
bucket: bucket.clone(),
206+
region: region.clone().or_else(|| Some("us-east-1".to_string())),
207+
part_size: 16 * 1024 * 1024,
208+
max_concurrency: 8,
209+
..Default::default()
210+
};
211+
s3_config.endpoint = Some(endpoint.clone());
212+
s3_config.force_path_style = true;
213+
214+
let s3_backend = S3Backend::with_config(s3_config)
215+
.await
216+
.map_err(|e| format!("Failed to create S3 backend: {}", e))?;
217+
let object_client = ObjectClient::new(s3_backend);
218+
let block_store = ObjectBlockStore::new(object_client);
219+
220+
let layout = ChunkLayout::default();
221+
let fs = VFS::new(layout, block_store, meta)
222+
.await
223+
.map_err(|e| format!("Failed to create VFS: {}", e))?;
224+
225+
println!("Mounting filesystem...");
226+
227+
let handle = mount_vfs_unprivileged(fs, &mount_point)
228+
.await
229+
.map_err(|e| format!("Failed to mount filesystem: {}", e))?;
230+
231+
println!(
232+
"SlayerFS with S3 backend successfully mounted at: {}",
233+
mount_point.display()
234+
);
235+
println!("Press Ctrl+C to exit and unmount filesystem...");
236+
237+
signal::ctrl_c().await?;
238+
println!("\nUnmounting filesystem...");
239+
240+
handle.unmount().await?;
241+
println!("Filesystem unmounted");
242+
Ok(())
243+
}
244+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
database:
2+
type: sqlite
3+
url: "sqlite://metadata.db?mode=rwc"

project/slayerfs/src/cadapter/s3.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ pub struct S3Config {
2828
pub retry_base_delay: u64,
2929
/// Enable MD5 checksums for uploads (default: true)
3030
pub enable_md5: bool,
31+
/// Custom endpoint URL (e.g. for MinIO or localstack)
32+
pub endpoint: Option<String>,
33+
/// Force path-style access (required for some S3-compatible services)
34+
pub force_path_style: bool,
3135
}
3236

3337
impl Default for S3Config {
@@ -40,6 +44,8 @@ impl Default for S3Config {
4044
max_retries: 3,
4145
retry_base_delay: 100,
4246
enable_md5: true,
47+
endpoint: None,
48+
force_path_style: false,
4349
}
4450
}
4551
}
@@ -79,7 +85,18 @@ impl S3Backend {
7985
}
8086

8187
let aws_config = aws_config_loader.load().await;
82-
let client = Client::new(&aws_config);
88+
89+
let mut s3_config_builder = aws_sdk_s3::config::Builder::from(&aws_config);
90+
91+
if let Some(endpoint) = &config.endpoint {
92+
s3_config_builder = s3_config_builder.endpoint_url(endpoint);
93+
}
94+
95+
if config.force_path_style {
96+
s3_config_builder = s3_config_builder.force_path_style(true);
97+
}
98+
99+
let client = Client::from_conf(s3_config_builder.build());
83100

84101
Ok(Self { client, config })
85102
}

0 commit comments

Comments
 (0)