Skip to content

Commit 364a8b2

Browse files
committed
storage: support external blob
Signed-off-by: Yan Song <[email protected]>
1 parent c030ca2 commit 364a8b2

File tree

14 files changed

+385
-27
lines changed

14 files changed

+385
-27
lines changed

Cargo.lock

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

storage/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ nydus-utils = { version = "0.4", path = "../utils", features = [
5050
"encryption",
5151
"zran",
5252
] }
53+
rmp-serde = "1.1.2"
5354

5455
[dev-dependencies]
5556
vmm-sys-util = "0.11"

storage/src/backend/external/local.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2024 Nydus Developers. All rights reserved.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use std::collections::HashMap;
6+
use std::io::Result;
7+
use std::os::unix::fs::FileExt;
8+
use std::path::PathBuf;
9+
10+
use crate::backend::external::{meta::MetaMap, ExternalBlobReader};
11+
use crate::device::BlobChunkInfo;
12+
13+
use serde::{Deserialize, Serialize};
14+
15+
#[derive(Debug, PartialEq, Deserialize, Serialize)]
16+
pub struct Object {
17+
#[serde(default, rename = "Path")]
18+
pub path: String,
19+
}
20+
21+
pub struct LocalBackend {
22+
meta_map: MetaMap,
23+
root: PathBuf,
24+
}
25+
26+
impl LocalBackend {
27+
pub fn new(meta_path: PathBuf, config: &HashMap<String, String>) -> Result<Self> {
28+
let meta_map = MetaMap::new(meta_path)?;
29+
let root = PathBuf::from(config.get("root").unwrap());
30+
Ok(Self { meta_map, root })
31+
}
32+
}
33+
34+
impl ExternalBlobReader for LocalBackend {
35+
fn read(&self, buf: &mut [u8], chunks: &[&dyn BlobChunkInfo]) -> Result<usize> {
36+
let chunk_index = chunks[0].id();
37+
let (object_bytes, chunk) = self.meta_map.get_object(chunk_index)?;
38+
39+
let object: Object = rmp_serde::from_slice(&object_bytes)
40+
.map_err(|_e| einval!("failed to deserialize object"))?;
41+
42+
let path = self.root.join(&object.path);
43+
44+
println!(
45+
"local_backend: path={:?}, object_offset={}, expected_size={}",
46+
path,
47+
chunk.object_offset,
48+
buf.len()
49+
);
50+
51+
let file = std::fs::File::open(path)?;
52+
file.read_exact_at(buf, chunk.object_offset)?;
53+
54+
Ok(buf.len())
55+
}
56+
}

storage/src/backend/external/meta.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2024 Nydus Developers. All rights reserved.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use std::io::Result;
6+
use std::mem::size_of;
7+
use std::path::Path;
8+
use std::{fs::File, os::unix::fs::MetadataExt};
9+
10+
use nydus_utils::filemap::FileMapState;
11+
use serde::{Deserialize, Serialize};
12+
13+
// Layout
14+
//
15+
// header: magic | version | chunk_meta_offset | object_meta_offset
16+
// chunks: chunk_meta | chunk | chunk | ...
17+
// objects: object_meta | [object_offsets] | object | object | ...
18+
19+
// 4096 bytes
20+
#[repr(C)]
21+
#[derive(Debug)]
22+
pub struct Header {
23+
magic: u32,
24+
version: u32,
25+
26+
chunk_meta_offset: u32,
27+
object_meta_offset: u32,
28+
29+
reserved: [u8; 4080],
30+
}
31+
32+
// 256 bytes
33+
#[repr(C)]
34+
#[derive(Debug)]
35+
pub struct ChunkMeta {
36+
entry_count: u32,
37+
entry_size: u32,
38+
39+
reserved: [u8; 248],
40+
}
41+
42+
// 256 bytes
43+
#[repr(C)]
44+
#[derive(Debug)]
45+
pub struct ObjectMeta {
46+
entry_count: u32,
47+
entry_size: u32,
48+
49+
reserved: [u8; 248],
50+
}
51+
52+
// 16 bytes
53+
#[repr(C)]
54+
#[derive(Debug)]
55+
pub struct Chunk {
56+
pub object_index: u32,
57+
reserved: [u8; 4],
58+
pub object_offset: u64,
59+
}
60+
61+
// 4 bytes
62+
pub type ObjectOffset = u32;
63+
64+
#[derive(Debug)]
65+
pub struct Object {
66+
entry_size: u32,
67+
encoded_data: Vec<u8>,
68+
}
69+
70+
#[derive(Debug, PartialEq, Deserialize, Serialize)]
71+
pub struct LocalObject {
72+
#[serde(default, rename = "Path")]
73+
pub path: String,
74+
}
75+
76+
pub struct MetaMap {
77+
map: FileMapState,
78+
}
79+
80+
impl MetaMap {
81+
pub fn new<P: AsRef<Path>>(meta_path: P) -> Result<Self> {
82+
let file = File::open(meta_path)?;
83+
let size = file.metadata()?.size() as usize;
84+
let map = FileMapState::new(file, 0, size, false)?;
85+
Ok(Self { map })
86+
}
87+
88+
pub fn get_object(&self, chunk_index: u32) -> Result<(&[u8], &Chunk)> {
89+
let header = self.map.get_ref::<Header>(0)?;
90+
let chunk_meta_offset = header.chunk_meta_offset;
91+
let object_meta_offset = header.object_meta_offset;
92+
93+
let chunk_meta = self.map.get_ref::<ChunkMeta>(chunk_meta_offset as usize)?;
94+
let object_meta = self
95+
.map
96+
.get_ref::<ObjectMeta>(object_meta_offset as usize)?;
97+
98+
let chunk = self.map.get_ref::<Chunk>(
99+
chunk_meta_offset as usize
100+
+ size_of::<ChunkMeta>()
101+
+ chunk_index as usize * chunk_meta.entry_size as usize,
102+
)?;
103+
let object_index = chunk.object_index;
104+
let object_offset = if object_meta.entry_size == 0 {
105+
let object_offset_offset = object_meta_offset as usize
106+
+ size_of::<ObjectMeta>()
107+
+ object_index as usize * size_of::<ObjectOffset>();
108+
*self.map.get_ref::<ObjectOffset>(object_offset_offset)? as usize
109+
} else {
110+
object_meta_offset as usize
111+
+ size_of::<ObjectMeta>()
112+
+ object_index as usize * object_meta.entry_size as usize
113+
};
114+
115+
let object_size = *self.map.get_ref::<u32>(object_offset)? as usize;
116+
117+
println!(
118+
"get_object: chunk_index={}, object_index={}, object_size={}",
119+
chunk_index, object_index, object_size,
120+
);
121+
122+
let object_data: &[u8] = self
123+
.map
124+
.get_slice(object_offset + size_of::<u32>(), object_size)?;
125+
126+
Ok((object_data, chunk))
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
#[test]
135+
fn test_meta_map() {
136+
let meta_map = MetaMap::new("/home/imeoer/backend.meta").unwrap();
137+
let (object_bytes, _) = meta_map.get_object(0).unwrap();
138+
let object: LocalObject = rmp_serde::from_slice(&object_bytes)
139+
.map_err(|e| einval!(format!("failed to deserialize object: {:?}", e)))
140+
.unwrap();
141+
println!("OBJECT {:?}", object);
142+
}
143+
}

storage/src/backend/external/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2024 Nydus Developers. All rights reserved.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use std::collections::HashMap;
6+
use std::fs::File;
7+
use std::io::Result;
8+
use std::path::PathBuf;
9+
use std::sync::Arc;
10+
11+
use crate::device::BlobChunkInfo;
12+
use serde::{Deserialize, Serialize};
13+
14+
pub mod local;
15+
pub mod meta;
16+
17+
pub trait ExternalBlobReader: Send + Sync {
18+
fn read(&self, buf: &mut [u8], chunks: &[&dyn BlobChunkInfo]) -> Result<usize>;
19+
}
20+
21+
#[derive(Debug, PartialEq, Deserialize, Serialize)]
22+
struct BackendConfig {
23+
#[serde(rename = "type")]
24+
kind: String,
25+
config: HashMap<String, String>,
26+
}
27+
28+
pub struct ExternalBackendFactory {}
29+
30+
impl ExternalBackendFactory {
31+
pub fn new(
32+
meta_path: PathBuf,
33+
backend_config_path: PathBuf,
34+
) -> Result<Arc<dyn ExternalBlobReader>> {
35+
let backend_config: BackendConfig =
36+
serde_json::from_reader(File::open(backend_config_path)?)?;
37+
match backend_config.kind.as_str() {
38+
"local" => {
39+
let backend = local::LocalBackend::new(meta_path, &backend_config.config)?;
40+
Ok(Arc::new(backend) as Arc<dyn ExternalBlobReader>)
41+
}
42+
_ => {
43+
return Err(einval!(format!(
44+
"unsupported backend type: {}",
45+
backend_config.kind
46+
)))
47+
}
48+
}
49+
}
50+
}

storage/src/backend/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use nydus_utils::{
2727
use crate::utils::{alloc_buf, copyv};
2828
use crate::StorageError;
2929

30+
pub mod external;
31+
3032
#[cfg(any(
3133
feature = "backend-oss",
3234
feature = "backend-registry",

0 commit comments

Comments
 (0)