Browse Source

test: add tests for EC idx dir separation, .vif preservation, and gRPC address parsing

- test_ec_encode_with_separate_idx_dir: EC encode reads .idx from separate dir
- test_ec_encode_fails_with_wrong_idx_dir: guards against ignoring idx_dir param
- test_destroy_preserves_vif: .vif survives volume destroy (needed by EC volumes)
- test_destroy_with_separate_idx_dir: destroy cleans both data and idx dirs
- test_parse_grpc_address_*: 5 tests for IP:port.grpcPort parsing including
  IPv4 dot regression that broke VolumeEcShardsCopy
rust-volume-server
Chris Lu 3 days ago
parent
commit
50f9890621
  1. 40
      seaweed-volume/src/server/grpc_server.rs
  2. 114
      seaweed-volume/src/storage/erasure_coding/ec_encoder.rs
  3. 84
      seaweed-volume/src/storage/volume.rs

40
seaweed-volume/src/server/grpc_server.rs

@ -3575,3 +3575,43 @@ fn get_process_rss_linux() -> Option<u64> {
}
Some(resident * page_size as u64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_grpc_address_with_explicit_grpc_port() {
// Format: "ip:port.grpcPort" — used by SeaweedFS for source_data_node
let result = parse_grpc_address("192.168.1.66:8080.18080").unwrap();
assert_eq!(result, "192.168.1.66:18080");
}
#[test]
fn test_parse_grpc_address_with_implicit_grpc_port() {
// Format: "ip:port" — grpc port = port + 10000
let result = parse_grpc_address("192.168.1.66:8080").unwrap();
assert_eq!(result, "192.168.1.66:18080");
}
#[test]
fn test_parse_grpc_address_localhost() {
let result = parse_grpc_address("localhost:9333").unwrap();
assert_eq!(result, "localhost:19333");
}
#[test]
fn test_parse_grpc_address_with_ipv4_dots() {
// Regression: naive split on '.' breaks on IP addresses
let result = parse_grpc_address("10.0.0.1:8080.18080").unwrap();
assert_eq!(result, "10.0.0.1:18080");
let result = parse_grpc_address("10.0.0.1:8080").unwrap();
assert_eq!(result, "10.0.0.1:18080");
}
#[test]
fn test_parse_grpc_address_invalid() {
assert!(parse_grpc_address("no-colon").is_err());
}
}

114
seaweed-volume/src/storage/erasure_coding/ec_encoder.rs

@ -541,4 +541,118 @@ mod tests {
assert_eq!(shard_opts[0].as_ref().unwrap(), &original_0);
assert_eq!(shard_opts[1].as_ref().unwrap(), &original_1);
}
/// EC encode must read .idx from a separate index directory when configured.
#[test]
fn test_ec_encode_with_separate_idx_dir() {
let dat_tmp = TempDir::new().unwrap();
let idx_tmp = TempDir::new().unwrap();
let dat_dir = dat_tmp.path().to_str().unwrap();
let idx_dir = idx_tmp.path().to_str().unwrap();
// Create a volume with separate data and index directories
let mut v = Volume::new(
dat_dir,
idx_dir,
"",
VolumeId(1),
NeedleMapKind::InMemory,
None,
None,
0,
Version::current(),
)
.unwrap();
for i in 1..=5 {
let data = format!("needle {} payload", i);
let mut n = Needle {
id: NeedleId(i),
cookie: Cookie(i as u32),
data: data.as_bytes().to_vec(),
data_size: data.len() as u32,
..Needle::default()
};
v.write_needle(&mut n, true).unwrap();
}
v.sync_to_disk().unwrap();
v.close();
// Verify .dat is in data dir, .idx is in idx dir
assert!(std::path::Path::new(&format!("{}/1.dat", dat_dir)).exists());
assert!(!std::path::Path::new(&format!("{}/1.idx", dat_dir)).exists());
assert!(std::path::Path::new(&format!("{}/1.idx", idx_dir)).exists());
assert!(!std::path::Path::new(&format!("{}/1.dat", idx_dir)).exists());
// EC encode with separate idx dir
let data_shards = 10;
let parity_shards = 4;
let total_shards = data_shards + parity_shards;
write_ec_files(dat_dir, idx_dir, "", VolumeId(1), data_shards, parity_shards).unwrap();
// Verify all 14 shard files in data dir
for i in 0..total_shards {
let path = format!("{}/1.ec{:02}", dat_dir, i);
assert!(
std::path::Path::new(&path).exists(),
"shard {} should exist in data dir",
path
);
}
// Verify .ecx in data dir (not idx dir)
assert!(std::path::Path::new(&format!("{}/1.ecx", dat_dir)).exists());
assert!(!std::path::Path::new(&format!("{}/1.ecx", idx_dir)).exists());
// Verify no shard files leaked into idx dir
for i in 0..total_shards {
let path = format!("{}/1.ec{:02}", idx_dir, i);
assert!(
!std::path::Path::new(&path).exists(),
"shard {} should NOT exist in idx dir",
path
);
}
}
/// EC encode should fail gracefully when .idx is only in the data dir
/// but we pass a wrong idx_dir. This guards against regressions where
/// write_ec_files ignores the idx_dir parameter.
#[test]
fn test_ec_encode_fails_with_wrong_idx_dir() {
let dat_tmp = TempDir::new().unwrap();
let idx_tmp = TempDir::new().unwrap();
let wrong_tmp = TempDir::new().unwrap();
let dat_dir = dat_tmp.path().to_str().unwrap();
let idx_dir = idx_tmp.path().to_str().unwrap();
let wrong_dir = wrong_tmp.path().to_str().unwrap();
let mut v = Volume::new(
dat_dir,
idx_dir,
"",
VolumeId(1),
NeedleMapKind::InMemory,
None,
None,
0,
Version::current(),
)
.unwrap();
let mut n = Needle {
id: NeedleId(1),
cookie: Cookie(1),
data: b"hello".to_vec(),
data_size: 5,
..Needle::default()
};
v.write_needle(&mut n, true).unwrap();
v.sync_to_disk().unwrap();
v.close();
// Should fail: .idx is in idx_dir, not wrong_dir
let result = write_ec_files(dat_dir, wrong_dir, "", VolumeId(1), 10, 4);
assert!(result.is_err(), "should fail when idx_dir doesn't contain .idx");
}
}

84
seaweed-volume/src/storage/volume.rs

@ -2759,4 +2759,88 @@ mod tests {
assert_eq!(info.data_size, data.len() as u32);
assert!(info.data_file_offset > 0);
}
/// Volume destroy must preserve .vif files (needed by EC volumes).
#[test]
fn test_destroy_preserves_vif() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().to_str().unwrap();
let mut v = make_test_volume(dir);
let mut n = Needle {
id: NeedleId(1),
cookie: Cookie(1),
data: b"test".to_vec(),
data_size: 4,
..Needle::default()
};
v.write_needle(&mut n, true).unwrap();
// Write a .vif file (as EC encode would)
let vif_path = format!("{}/1.vif", dir);
std::fs::write(&vif_path, r#"{"version":3}"#).unwrap();
assert!(std::path::Path::new(&vif_path).exists());
// .dat and .idx should exist
let dat_path = format!("{}/1.dat", dir);
let idx_path = format!("{}/1.idx", dir);
assert!(std::path::Path::new(&dat_path).exists());
assert!(std::path::Path::new(&idx_path).exists());
// Destroy the volume
v.destroy().unwrap();
// .dat and .idx should be gone
assert!(!std::path::Path::new(&dat_path).exists(), ".dat should be removed");
assert!(!std::path::Path::new(&idx_path).exists(), ".idx should be removed");
// .vif MUST be preserved for EC volumes
assert!(std::path::Path::new(&vif_path).exists(), ".vif must survive destroy");
}
/// Volume destroy with separate idx directory must clean up both dirs.
#[test]
fn test_destroy_with_separate_idx_dir() {
let dat_tmp = TempDir::new().unwrap();
let idx_tmp = TempDir::new().unwrap();
let dat_dir = dat_tmp.path().to_str().unwrap();
let idx_dir = idx_tmp.path().to_str().unwrap();
let mut v = Volume::new(
dat_dir,
idx_dir,
"",
VolumeId(1),
NeedleMapKind::InMemory,
None,
None,
0,
Version::current(),
)
.unwrap();
let mut n = Needle {
id: NeedleId(1),
cookie: Cookie(1),
data: b"hello".to_vec(),
data_size: 5,
..Needle::default()
};
v.write_needle(&mut n, true).unwrap();
// Write .vif in data dir (as EC encode would)
let vif_path = format!("{}/1.vif", dat_dir);
std::fs::write(&vif_path, r#"{"version":3}"#).unwrap();
let dat_path = format!("{}/1.dat", dat_dir);
let idx_path = format!("{}/1.idx", idx_dir);
assert!(std::path::Path::new(&dat_path).exists());
assert!(std::path::Path::new(&idx_path).exists());
v.destroy().unwrap();
assert!(!std::path::Path::new(&dat_path).exists(), ".dat removed from data dir");
assert!(!std::path::Path::new(&idx_path).exists(), ".idx removed from idx dir");
assert!(std::path::Path::new(&vif_path).exists(), ".vif preserved in data dir");
}
}
Loading…
Cancel
Save