|
|
|
@ -10,8 +10,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; |
|
|
|
|
|
|
|
use crate::config::MinFreeSpace;
|
|
|
|
use crate::storage::disk_location::DiskLocation;
|
|
|
|
use crate::storage::erasure_coding::ec_volume::EcVolume;
|
|
|
|
use crate::storage::erasure_coding::ec_shard::EcVolumeShard;
|
|
|
|
use crate::storage::erasure_coding::ec_volume::EcVolume;
|
|
|
|
use crate::storage::needle::needle::Needle;
|
|
|
|
use crate::storage::needle_map::NeedleMapKind;
|
|
|
|
use crate::storage::super_block::ReplicaPlacement;
|
|
|
|
@ -23,6 +23,7 @@ pub struct Store { |
|
|
|
pub locations: Vec<DiskLocation>,
|
|
|
|
pub needle_map_kind: NeedleMapKind,
|
|
|
|
pub volume_size_limit: AtomicU64,
|
|
|
|
pub id: String,
|
|
|
|
pub ip: String,
|
|
|
|
pub port: u16,
|
|
|
|
pub grpc_port: u16,
|
|
|
|
@ -38,6 +39,7 @@ impl Store { |
|
|
|
locations: Vec::new(),
|
|
|
|
needle_map_kind,
|
|
|
|
volume_size_limit: AtomicU64::new(0),
|
|
|
|
id: String::new(),
|
|
|
|
ip: String::new(),
|
|
|
|
port: 0,
|
|
|
|
grpc_port: 0,
|
|
|
|
@ -56,8 +58,16 @@ impl Store { |
|
|
|
max_volume_count: i32,
|
|
|
|
disk_type: DiskType,
|
|
|
|
min_free_space: MinFreeSpace,
|
|
|
|
tags: Vec<String>,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
let mut loc = DiskLocation::new(directory, idx_directory, max_volume_count, disk_type, min_free_space);
|
|
|
|
let mut loc = DiskLocation::new(
|
|
|
|
directory,
|
|
|
|
idx_directory,
|
|
|
|
max_volume_count,
|
|
|
|
disk_type,
|
|
|
|
min_free_space,
|
|
|
|
tags,
|
|
|
|
)?;
|
|
|
|
loc.load_existing_volumes(self.needle_map_kind)?;
|
|
|
|
|
|
|
|
// Check for duplicate volume IDs across existing locations
|
|
|
|
@ -65,7 +75,10 @@ impl Store { |
|
|
|
if self.find_volume(vid).is_some() {
|
|
|
|
return Err(io::Error::new(
|
|
|
|
io::ErrorKind::AlreadyExists,
|
|
|
|
format!("volume {} already exists in another location, conflicting dir: {}", vid, directory),
|
|
|
|
format!(
|
|
|
|
"volume {} already exists in another location, conflicting dir: {}",
|
|
|
|
vid, directory
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -87,7 +100,10 @@ impl Store { |
|
|
|
}
|
|
|
|
|
|
|
|
/// Find which location contains a volume (mutable).
|
|
|
|
pub fn find_volume_mut(&mut self, vid: VolumeId) -> Option<(usize, &mut crate::storage::volume::Volume)> {
|
|
|
|
pub fn find_volume_mut(
|
|
|
|
&mut self,
|
|
|
|
vid: VolumeId,
|
|
|
|
) -> Option<(usize, &mut crate::storage::volume::Volume)> {
|
|
|
|
for (i, loc) in self.locations.iter_mut().enumerate() {
|
|
|
|
if let Some(v) = loc.find_volume_mut(vid) {
|
|
|
|
return Some((i, v));
|
|
|
|
@ -145,8 +161,12 @@ impl Store { |
|
|
|
})?;
|
|
|
|
|
|
|
|
self.locations[loc_idx].create_volume(
|
|
|
|
vid, collection, self.needle_map_kind,
|
|
|
|
replica_placement, ttl, preallocate,
|
|
|
|
vid,
|
|
|
|
collection,
|
|
|
|
self.needle_map_kind,
|
|
|
|
replica_placement,
|
|
|
|
ttl,
|
|
|
|
preallocate,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -185,15 +205,10 @@ impl Store { |
|
|
|
if &loc.disk_type != &disk_type {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let base = crate::storage::volume::volume_file_name(
|
|
|
|
&loc.directory, collection, vid,
|
|
|
|
);
|
|
|
|
let base = crate::storage::volume::volume_file_name(&loc.directory, collection, vid);
|
|
|
|
let dat_path = format!("{}.dat", base);
|
|
|
|
if std::path::Path::new(&dat_path).exists() {
|
|
|
|
return loc.create_volume(
|
|
|
|
vid, collection, self.needle_map_kind,
|
|
|
|
None, None, 0,
|
|
|
|
);
|
|
|
|
return loc.create_volume(vid, collection, self.needle_map_kind, None, None, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(VolumeError::Io(io::Error::new(
|
|
|
|
@ -211,14 +226,22 @@ impl Store { |
|
|
|
}
|
|
|
|
|
|
|
|
/// Read a needle from a volume, optionally reading deleted needles.
|
|
|
|
pub fn read_volume_needle_opt(&self, vid: VolumeId, n: &mut Needle, read_deleted: bool) -> Result<i32, VolumeError> {
|
|
|
|
pub fn read_volume_needle_opt(
|
|
|
|
&self,
|
|
|
|
vid: VolumeId,
|
|
|
|
n: &mut Needle,
|
|
|
|
read_deleted: bool,
|
|
|
|
) -> Result<i32, VolumeError> {
|
|
|
|
let (_, vol) = self.find_volume(vid).ok_or(VolumeError::NotFound)?;
|
|
|
|
vol.read_needle_opt(n, read_deleted)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Read needle metadata and return streaming info for large file reads.
|
|
|
|
pub fn read_volume_needle_stream_info(
|
|
|
|
&self, vid: VolumeId, n: &mut Needle, read_deleted: bool,
|
|
|
|
&self,
|
|
|
|
vid: VolumeId,
|
|
|
|
n: &mut Needle,
|
|
|
|
read_deleted: bool,
|
|
|
|
) -> Result<crate::storage::volume::NeedleStreamInfo, VolumeError> {
|
|
|
|
let (_, vol) = self.find_volume(vid).ok_or(VolumeError::NotFound)?;
|
|
|
|
vol.read_needle_stream_info(n, read_deleted)
|
|
|
|
@ -226,12 +249,20 @@ impl Store { |
|
|
|
|
|
|
|
/// Write a needle to a volume.
|
|
|
|
pub fn write_volume_needle(
|
|
|
|
&mut self, vid: VolumeId, n: &mut Needle,
|
|
|
|
&mut self,
|
|
|
|
vid: VolumeId,
|
|
|
|
n: &mut Needle,
|
|
|
|
) -> Result<(u64, Size, bool), VolumeError> {
|
|
|
|
// Check disk space on the location containing this volume.
|
|
|
|
// We do this before the mutable borrow to avoid borrow conflicts.
|
|
|
|
let loc_idx = self.find_volume(vid).map(|(i, _)| i).ok_or(VolumeError::NotFound)?;
|
|
|
|
if self.locations[loc_idx].is_disk_space_low.load(Ordering::Relaxed) {
|
|
|
|
let loc_idx = self
|
|
|
|
.find_volume(vid)
|
|
|
|
.map(|(i, _)| i)
|
|
|
|
.ok_or(VolumeError::NotFound)?;
|
|
|
|
if self.locations[loc_idx]
|
|
|
|
.is_disk_space_low
|
|
|
|
.load(Ordering::Relaxed)
|
|
|
|
{
|
|
|
|
return Err(VolumeError::ReadOnly);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -241,7 +272,9 @@ impl Store { |
|
|
|
|
|
|
|
/// Delete a needle from a volume.
|
|
|
|
pub fn delete_volume_needle(
|
|
|
|
&mut self, vid: VolumeId, n: &mut Needle,
|
|
|
|
&mut self,
|
|
|
|
vid: VolumeId,
|
|
|
|
n: &mut Needle,
|
|
|
|
) -> Result<Size, VolumeError> {
|
|
|
|
let (_, vol) = self.find_volume_mut(vid).ok_or(VolumeError::NotFound)?;
|
|
|
|
vol.delete_needle(n)
|
|
|
|
@ -265,21 +298,25 @@ impl Store { |
|
|
|
|
|
|
|
/// Total max volumes across all locations.
|
|
|
|
pub fn max_volume_count(&self) -> i32 {
|
|
|
|
self.locations.iter()
|
|
|
|
self.locations
|
|
|
|
.iter()
|
|
|
|
.map(|loc| loc.max_volume_count.load(Ordering::Relaxed))
|
|
|
|
.sum()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Free volume slots across all locations.
|
|
|
|
pub fn free_volume_count(&self) -> i32 {
|
|
|
|
self.locations.iter()
|
|
|
|
self.locations
|
|
|
|
.iter()
|
|
|
|
.map(|loc| loc.free_volume_count())
|
|
|
|
.sum()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// All volume IDs across all locations.
|
|
|
|
pub fn all_volume_ids(&self) -> Vec<VolumeId> {
|
|
|
|
let mut ids: Vec<VolumeId> = self.locations.iter()
|
|
|
|
let mut ids: Vec<VolumeId> = self
|
|
|
|
.locations
|
|
|
|
.iter()
|
|
|
|
.flat_map(|loc| loc.volume_ids())
|
|
|
|
.collect();
|
|
|
|
ids.sort();
|
|
|
|
@ -297,15 +334,17 @@ impl Store { |
|
|
|
shard_ids: &[u32],
|
|
|
|
) -> Result<(), VolumeError> {
|
|
|
|
// Find the directory where the EC files live
|
|
|
|
let dir = self.find_ec_dir(vid, collection)
|
|
|
|
.ok_or_else(|| VolumeError::Io(io::Error::new(
|
|
|
|
let dir = self.find_ec_dir(vid, collection).ok_or_else(|| {
|
|
|
|
VolumeError::Io(io::Error::new(
|
|
|
|
io::ErrorKind::NotFound,
|
|
|
|
format!("ec volume {} shards not found on disk", vid),
|
|
|
|
)))?;
|
|
|
|
))
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let ec_vol = self.ec_volumes.entry(vid).or_insert_with(|| {
|
|
|
|
EcVolume::new(&dir, &dir, collection, vid).unwrap()
|
|
|
|
});
|
|
|
|
let ec_vol = self
|
|
|
|
.ec_volumes
|
|
|
|
.entry(vid)
|
|
|
|
.or_insert_with(|| EcVolume::new(&dir, &dir, collection, vid).unwrap());
|
|
|
|
|
|
|
|
for &shard_id in shard_ids {
|
|
|
|
let shard = EcVolumeShard::new(&dir, collection, vid, shard_id as u8);
|
|
|
|
@ -316,11 +355,7 @@ impl Store { |
|
|
|
}
|
|
|
|
|
|
|
|
/// Unmount EC shards for a volume.
|
|
|
|
pub fn unmount_ec_shards(
|
|
|
|
&mut self,
|
|
|
|
vid: VolumeId,
|
|
|
|
shard_ids: &[u32],
|
|
|
|
) {
|
|
|
|
pub fn unmount_ec_shards(&mut self, vid: VolumeId, shard_ids: &[u32]) {
|
|
|
|
if let Some(ec_vol) = self.ec_volumes.get_mut(&vid) {
|
|
|
|
for &shard_id in shard_ids {
|
|
|
|
ec_vol.remove_shard(shard_id as u8);
|
|
|
|
@ -333,12 +368,7 @@ impl Store { |
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete EC shard files from disk.
|
|
|
|
pub fn delete_ec_shards(
|
|
|
|
&mut self,
|
|
|
|
vid: VolumeId,
|
|
|
|
collection: &str,
|
|
|
|
shard_ids: &[u32],
|
|
|
|
) {
|
|
|
|
pub fn delete_ec_shards(&mut self, vid: VolumeId, collection: &str, shard_ids: &[u32]) {
|
|
|
|
// Delete shard files from disk
|
|
|
|
for loc in &self.locations {
|
|
|
|
for &shard_id in shard_ids {
|
|
|
|
@ -355,7 +385,8 @@ impl Store { |
|
|
|
let all_gone = self.check_all_ec_shards_deleted(vid, collection);
|
|
|
|
if all_gone {
|
|
|
|
for loc in &self.locations {
|
|
|
|
let base = crate::storage::volume::volume_file_name(&loc.directory, collection, vid);
|
|
|
|
let base =
|
|
|
|
crate::storage::volume::volume_file_name(&loc.directory, collection, vid);
|
|
|
|
let _ = std::fs::remove_file(format!("{}.ecx", base));
|
|
|
|
let _ = std::fs::remove_file(format!("{}.ecj", base));
|
|
|
|
}
|
|
|
|
@ -388,7 +419,12 @@ impl Store { |
|
|
|
}
|
|
|
|
|
|
|
|
/// Find the directory containing a specific EC shard file.
|
|
|
|
pub fn find_ec_shard_dir(&self, vid: VolumeId, collection: &str, shard_id: u8) -> Option<String> {
|
|
|
|
pub fn find_ec_shard_dir(
|
|
|
|
&self,
|
|
|
|
vid: VolumeId,
|
|
|
|
collection: &str,
|
|
|
|
shard_id: u8,
|
|
|
|
) -> Option<String> {
|
|
|
|
for loc in &self.locations {
|
|
|
|
let shard = EcVolumeShard::new(&loc.directory, collection, vid, shard_id);
|
|
|
|
if std::path::Path::new(&shard.file_name()).exists() {
|
|
|
|
@ -405,7 +441,10 @@ impl Store { |
|
|
|
if let Some((_, v)) = self.find_volume(vid) {
|
|
|
|
Ok(v.garbage_level())
|
|
|
|
} else {
|
|
|
|
Err(format!("volume id {} is not found during check compact", vid.0))
|
|
|
|
Err(format!(
|
|
|
|
"volume id {} is not found during check compact",
|
|
|
|
vid.0
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -437,7 +476,10 @@ impl Store { |
|
|
|
let volume_size = v.dat_file_size().unwrap_or(0);
|
|
|
|
Ok((is_read_only, volume_size))
|
|
|
|
} else {
|
|
|
|
Err(format!("volume id {} is not found during commit compact", vid.0))
|
|
|
|
Err(format!(
|
|
|
|
"volume id {} is not found during commit compact",
|
|
|
|
vid.0
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -447,7 +489,10 @@ impl Store { |
|
|
|
v.cleanup_compact()
|
|
|
|
.map_err(|e| format!("cleanup volume {}: {}", vid.0, e))
|
|
|
|
} else {
|
|
|
|
Err(format!("volume id {} is not found during cleaning up", vid.0))
|
|
|
|
Err(format!(
|
|
|
|
"volume id {} is not found during cleaning up",
|
|
|
|
vid.0
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ -475,7 +520,16 @@ mod tests { |
|
|
|
fn make_test_store(dirs: &[&str]) -> Store {
|
|
|
|
let mut store = Store::new(NeedleMapKind::InMemory);
|
|
|
|
for dir in dirs {
|
|
|
|
store.add_location(dir, dir, 10, DiskType::HardDrive, MinFreeSpace::Percent(1.0)).unwrap();
|
|
|
|
store
|
|
|
|
.add_location(
|
|
|
|
dir,
|
|
|
|
dir,
|
|
|
|
10,
|
|
|
|
DiskType::HardDrive,
|
|
|
|
MinFreeSpace::Percent(1.0),
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
store
|
|
|
|
}
|
|
|
|
@ -486,7 +540,16 @@ mod tests { |
|
|
|
let dir = tmp.path().to_str().unwrap();
|
|
|
|
|
|
|
|
let mut store = Store::new(NeedleMapKind::InMemory);
|
|
|
|
store.add_location(dir, dir, 10, DiskType::HardDrive, MinFreeSpace::Percent(1.0)).unwrap();
|
|
|
|
store
|
|
|
|
.add_location(
|
|
|
|
dir,
|
|
|
|
dir,
|
|
|
|
10,
|
|
|
|
DiskType::HardDrive,
|
|
|
|
MinFreeSpace::Percent(1.0),
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(store.locations.len(), 1);
|
|
|
|
assert_eq!(store.max_volume_count(), 10);
|
|
|
|
}
|
|
|
|
@ -497,7 +560,9 @@ mod tests { |
|
|
|
let dir = tmp.path().to_str().unwrap();
|
|
|
|
let mut store = make_test_store(&[dir]);
|
|
|
|
|
|
|
|
store.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
assert!(store.has_volume(VolumeId(1)));
|
|
|
|
assert!(!store.has_volume(VolumeId(2)));
|
|
|
|
assert_eq!(store.total_volume_count(), 1);
|
|
|
|
@ -508,7 +573,9 @@ mod tests { |
|
|
|
let tmp = TempDir::new().unwrap();
|
|
|
|
let dir = tmp.path().to_str().unwrap();
|
|
|
|
let mut store = make_test_store(&[dir]);
|
|
|
|
store.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
// Write
|
|
|
|
let mut n = Needle {
|
|
|
|
@ -523,13 +590,20 @@ mod tests { |
|
|
|
assert!(offset > 0);
|
|
|
|
|
|
|
|
// Read
|
|
|
|
let mut read_n = Needle { id: NeedleId(1), ..Needle::default() };
|
|
|
|
let mut read_n = Needle {
|
|
|
|
id: NeedleId(1),
|
|
|
|
..Needle::default()
|
|
|
|
};
|
|
|
|
let count = store.read_volume_needle(VolumeId(1), &mut read_n).unwrap();
|
|
|
|
assert_eq!(count, 11);
|
|
|
|
assert_eq!(read_n.data, b"hello store");
|
|
|
|
|
|
|
|
// Delete
|
|
|
|
let mut del_n = Needle { id: NeedleId(1), cookie: Cookie(0xaa), ..Needle::default() };
|
|
|
|
let mut del_n = Needle {
|
|
|
|
id: NeedleId(1),
|
|
|
|
cookie: Cookie(0xaa),
|
|
|
|
..Needle::default()
|
|
|
|
};
|
|
|
|
let deleted = store.delete_volume_needle(VolumeId(1), &mut del_n).unwrap();
|
|
|
|
assert!(deleted.0 > 0);
|
|
|
|
}
|
|
|
|
@ -542,13 +616,35 @@ mod tests { |
|
|
|
let dir2 = tmp2.path().to_str().unwrap();
|
|
|
|
|
|
|
|
let mut store = Store::new(NeedleMapKind::InMemory);
|
|
|
|
store.add_location(dir1, dir1, 5, DiskType::HardDrive, MinFreeSpace::Percent(1.0)).unwrap();
|
|
|
|
store.add_location(dir2, dir2, 5, DiskType::HardDrive, MinFreeSpace::Percent(1.0)).unwrap();
|
|
|
|
store
|
|
|
|
.add_location(
|
|
|
|
dir1,
|
|
|
|
dir1,
|
|
|
|
5,
|
|
|
|
DiskType::HardDrive,
|
|
|
|
MinFreeSpace::Percent(1.0),
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.add_location(
|
|
|
|
dir2,
|
|
|
|
dir2,
|
|
|
|
5,
|
|
|
|
DiskType::HardDrive,
|
|
|
|
MinFreeSpace::Percent(1.0),
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(store.max_volume_count(), 10);
|
|
|
|
|
|
|
|
// Add volumes — should go to location with fewest volumes
|
|
|
|
store.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store.add_volume(VolumeId(2), "", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(1), "", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(2), "", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(store.total_volume_count(), 2);
|
|
|
|
// Both locations should have 1 volume each (load-balanced)
|
|
|
|
@ -562,9 +658,15 @@ mod tests { |
|
|
|
let dir = tmp.path().to_str().unwrap();
|
|
|
|
let mut store = make_test_store(&[dir]);
|
|
|
|
|
|
|
|
store.add_volume(VolumeId(1), "pics", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store.add_volume(VolumeId(2), "pics", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store.add_volume(VolumeId(3), "docs", None, None, 0, DiskType::HardDrive).unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(1), "pics", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(2), "pics", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.add_volume(VolumeId(3), "docs", None, None, 0, DiskType::HardDrive)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(store.total_volume_count(), 3);
|
|
|
|
|
|
|
|
store.delete_collection("pics");
|
|
|
|
@ -578,7 +680,10 @@ mod tests { |
|
|
|
let dir = tmp.path().to_str().unwrap();
|
|
|
|
let store = make_test_store(&[dir]);
|
|
|
|
|
|
|
|
let mut n = Needle { id: NeedleId(1), ..Needle::default() };
|
|
|
|
let mut n = Needle {
|
|
|
|
id: NeedleId(1),
|
|
|
|
..Needle::default()
|
|
|
|
};
|
|
|
|
let err = store.read_volume_needle(VolumeId(99), &mut n);
|
|
|
|
assert!(matches!(err, Err(VolumeError::NotFound)));
|
|
|
|
}
|
|
|
|
|