[functional-tests] Move thin_check functional tests to Rust.
They'll be run as part of 'cargo test' now.
This commit is contained in:
parent
7243f95380
commit
fcfcc60b89
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -107,6 +107,18 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "duct"
|
||||||
|
version = "0.13.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f90a9c3a25aafbd538c7d40a53f83c4487ee8216c12d1c8ef2c01eb2f6ea1553"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"os_pipe",
|
||||||
|
"shared_child",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -260,6 +272,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -369,6 +387,16 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "0.4.23"
|
version = "0.4.23"
|
||||||
@ -543,6 +571,16 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shared_child"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cebcf3a403e4deafaf34dc882c4a1b6a648b43e5670aa2e4bb985914eaeb2d2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@ -603,10 +641,12 @@ dependencies = [
|
|||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
"crc32c",
|
"crc32c",
|
||||||
|
"duct",
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"io-uring",
|
"io-uring",
|
||||||
|
"json",
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"nom",
|
"nom",
|
||||||
|
@ -10,6 +10,7 @@ anyhow = "1.0"
|
|||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
crc32c = "0.4"
|
crc32c = "0.4"
|
||||||
|
duct = "0.13"
|
||||||
fixedbitset = "0.3"
|
fixedbitset = "0.3"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
@ -27,5 +28,6 @@ threadpool = "1.8"
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
json = "0.12"
|
||||||
quickcheck = "0.9"
|
quickcheck = "0.9"
|
||||||
quickcheck_macros = "0.9"
|
quickcheck_macros = "0.9"
|
||||||
|
@ -67,86 +67,6 @@
|
|||||||
;; to run.
|
;; to run.
|
||||||
(define (register-thin-tests) #t)
|
(define (register-thin-tests) #t)
|
||||||
|
|
||||||
;;;-----------------------------------------------------------
|
|
||||||
;;; thin_check scenarios
|
|
||||||
;;;-----------------------------------------------------------
|
|
||||||
|
|
||||||
(define-scenario (thin-check v)
|
|
||||||
"thin_check -V"
|
|
||||||
(run-ok-rcv (stdout _) (thin-check "-V")
|
|
||||||
(assert-equal tools-version stdout)))
|
|
||||||
|
|
||||||
(define-scenario (thin-check version)
|
|
||||||
"thin_check --version"
|
|
||||||
(run-ok-rcv (stdout _) (thin-check "--version")
|
|
||||||
(assert-equal tools-version stdout)))
|
|
||||||
|
|
||||||
(define-scenario (thin-check h)
|
|
||||||
"print help (-h)"
|
|
||||||
(run-ok-rcv (stdout _) (thin-check "-h")
|
|
||||||
(assert-equal thin-check-help stdout)))
|
|
||||||
|
|
||||||
(define-scenario (thin-check help)
|
|
||||||
"print help (--help)"
|
|
||||||
(run-ok-rcv (stdout _) (thin-check "--help")
|
|
||||||
(assert-equal thin-check-help stdout)))
|
|
||||||
|
|
||||||
(define-scenario (thin-check bad-option)
|
|
||||||
"Unrecognised option should cause failure"
|
|
||||||
(run-fail (thin-check "--hedgehogs-only")))
|
|
||||||
|
|
||||||
(define-scenario (thin-check superblock-only-valid)
|
|
||||||
"--super-block-only check passes on valid metadata"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok (thin-check "--super-block-only" md))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check superblock-only-invalid)
|
|
||||||
"--super-block-only check fails with corrupt metadata"
|
|
||||||
(with-corrupt-metadata (md)
|
|
||||||
(run-fail (thin-check "--super-block-only" md))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check skip-mappings-valid)
|
|
||||||
"--skip-mappings check passes on valid metadata"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok (thin-check "--skip-mappings" md))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check ignore-non-fatal-errors)
|
|
||||||
"--ignore-non-fatal-errors check passes on valid metadata"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok (thin-check "--ignore-non-fatal-errors" md))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check quiet)
|
|
||||||
"--quiet should give no output"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok-rcv (stdout stderr) (thin-check "--quiet" md)
|
|
||||||
(assert-eof stdout)
|
|
||||||
(assert-eof stderr))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check clear-needs-check-flag)
|
|
||||||
"Accepts --clear-needs-check-flag"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok (thin-check "--clear-needs-check-flag" md))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check tiny-metadata)
|
|
||||||
"Prints helpful message in case tiny metadata given"
|
|
||||||
(with-temp-file-sized ((md "thin.bin" 1024))
|
|
||||||
(run-fail-rcv (_ stderr) (thin-check md)
|
|
||||||
(assert-starts-with "Metadata device/file too small. Is this binary metadata?" stderr))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check spot-accidental-xml-data)
|
|
||||||
"Prints helpful message if XML metadata given"
|
|
||||||
(with-thin-xml (xml)
|
|
||||||
(system (fmt #f "man bash >> " xml))
|
|
||||||
(run-fail-rcv (_ stderr) (thin-check xml)
|
|
||||||
(assert-matches ".*This looks like XML. thin_check only checks the binary metadata format." stderr))))
|
|
||||||
|
|
||||||
(define-scenario (thin-check info-fields)
|
|
||||||
"Outputs info fields"
|
|
||||||
(with-valid-metadata (md)
|
|
||||||
(run-ok-rcv (stdout stderr) (thin-check md)
|
|
||||||
(assert-matches ".*TRANSACTION_ID=[0-9]+.*" stdout)
|
|
||||||
(assert-matches ".*METADATA_FREE_BLOCKS=[0-9]+.*" stdout))))
|
|
||||||
|
|
||||||
;;;-----------------------------------------------------------
|
;;;-----------------------------------------------------------
|
||||||
;;; thin_restore scenarios
|
;;; thin_restore scenarios
|
||||||
;;;-----------------------------------------------------------
|
;;;-----------------------------------------------------------
|
||||||
|
11
tests/common/mod.rs
Normal file
11
tests/common/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub mod xml_generator;
|
||||||
|
|
||||||
|
pub fn mk_path(dir: &Path, file: &str) -> PathBuf {
|
||||||
|
let mut p = PathBuf::new();
|
||||||
|
p.push(dir);
|
||||||
|
p.push(PathBuf::from(file));
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
539
tests/common/xml_generator.rs
Normal file
539
tests/common/xml_generator.rs
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use rand::prelude::*;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::path::Path;
|
||||||
|
use thinp::thin::xml;
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
pub trait XmlGen {
|
||||||
|
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_xml(path: &Path, g: &mut dyn XmlGen) -> Result<()> {
|
||||||
|
let xml_out = OpenOptions::new()
|
||||||
|
.read(false)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(path)?;
|
||||||
|
let mut w = xml::XmlWriter::new(xml_out);
|
||||||
|
|
||||||
|
g.generate_xml(&mut w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn common_sb(nr_blocks: u64) -> xml::Superblock {
|
||||||
|
xml::Superblock {
|
||||||
|
uuid: "".to_string(),
|
||||||
|
time: 0,
|
||||||
|
transaction: 0,
|
||||||
|
flags: None,
|
||||||
|
version: None,
|
||||||
|
data_block_size: 32,
|
||||||
|
nr_data_blocks: nr_blocks,
|
||||||
|
metadata_snap: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
pub struct EmptyPoolS {}
|
||||||
|
|
||||||
|
impl XmlGen for EmptyPoolS {
|
||||||
|
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
||||||
|
v.superblock_b(&common_sb(1024))?;
|
||||||
|
v.superblock_e()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
pub struct SingleThinS {
|
||||||
|
pub offset: u64,
|
||||||
|
pub len: u64,
|
||||||
|
pub old_nr_data_blocks: u64,
|
||||||
|
pub new_nr_data_blocks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SingleThinS {
|
||||||
|
pub fn new(offset: u64, len: u64, old_nr_data_blocks: u64, new_nr_data_blocks: u64) -> Self {
|
||||||
|
SingleThinS {
|
||||||
|
offset,
|
||||||
|
len,
|
||||||
|
old_nr_data_blocks,
|
||||||
|
new_nr_data_blocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XmlGen for SingleThinS {
|
||||||
|
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
||||||
|
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
||||||
|
v.device_b(&xml::Device {
|
||||||
|
dev_id: 0,
|
||||||
|
mapped_blocks: self.len,
|
||||||
|
transaction: 0,
|
||||||
|
creation_time: 0,
|
||||||
|
snap_time: 0,
|
||||||
|
})?;
|
||||||
|
v.map(&xml::Map {
|
||||||
|
thin_begin: 0,
|
||||||
|
data_begin: self.offset,
|
||||||
|
time: 0,
|
||||||
|
len: self.len,
|
||||||
|
})?;
|
||||||
|
v.device_e()?;
|
||||||
|
v.superblock_e()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
pub struct FragmentedS {
|
||||||
|
pub nr_thins: u32,
|
||||||
|
pub thin_size: u64,
|
||||||
|
pub old_nr_data_blocks: u64,
|
||||||
|
pub new_nr_data_blocks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FragmentedS {
|
||||||
|
pub fn new(nr_thins: u32, thin_size: u64) -> Self {
|
||||||
|
let old_size = (nr_thins as u64) * thin_size;
|
||||||
|
FragmentedS {
|
||||||
|
nr_thins,
|
||||||
|
thin_size,
|
||||||
|
old_nr_data_blocks: (nr_thins as u64) * thin_size,
|
||||||
|
new_nr_data_blocks: old_size * 3 / 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ThinRun {
|
||||||
|
thin_id: u32,
|
||||||
|
thin_begin: u64,
|
||||||
|
len: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
|
struct MappedRun {
|
||||||
|
thin_id: u32,
|
||||||
|
thin_begin: u64,
|
||||||
|
data_begin: u64,
|
||||||
|
len: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_runs(thin_id: u32, total_len: u64, run_len: std::ops::Range<u64>) -> Vec<ThinRun> {
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
let mut b = 0u64;
|
||||||
|
while b < total_len {
|
||||||
|
let len = u64::min(
|
||||||
|
total_len - b,
|
||||||
|
thread_rng().gen_range(run_len.start, run_len.end),
|
||||||
|
);
|
||||||
|
runs.push(ThinRun {
|
||||||
|
thin_id: thin_id,
|
||||||
|
thin_begin: b,
|
||||||
|
len,
|
||||||
|
});
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
runs
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XmlGen for FragmentedS {
|
||||||
|
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
||||||
|
// Allocate each thin fully, in runs between 1 and 16.
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
for thin in 0..self.nr_thins {
|
||||||
|
runs.append(&mut mk_runs(thin, self.thin_size, 1..17));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle
|
||||||
|
runs.shuffle(&mut rand::thread_rng());
|
||||||
|
|
||||||
|
// map across the data
|
||||||
|
let mut maps = Vec::new();
|
||||||
|
let mut b = 0;
|
||||||
|
for r in &runs {
|
||||||
|
maps.push(MappedRun {
|
||||||
|
thin_id: r.thin_id,
|
||||||
|
thin_begin: r.thin_begin,
|
||||||
|
data_begin: b,
|
||||||
|
len: r.len,
|
||||||
|
});
|
||||||
|
b += r.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop half the mappings, which leaves us free runs
|
||||||
|
let mut dropped = Vec::new();
|
||||||
|
for i in 0..maps.len() {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
dropped.push(maps[i].clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unshuffle. This isn't strictly necc. but makes the xml
|
||||||
|
// more readable.
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
maps.sort_by(|&l, &r| match l.thin_id.cmp(&r.thin_id) {
|
||||||
|
Ordering::Equal => l.thin_begin.cmp(&r.thin_begin),
|
||||||
|
o => o,
|
||||||
|
});
|
||||||
|
|
||||||
|
// write the xml
|
||||||
|
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
||||||
|
for thin in 0..self.nr_thins {
|
||||||
|
v.device_b(&xml::Device {
|
||||||
|
dev_id: thin,
|
||||||
|
mapped_blocks: self.thin_size,
|
||||||
|
transaction: 0,
|
||||||
|
creation_time: 0,
|
||||||
|
snap_time: 0,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for m in &dropped {
|
||||||
|
if m.thin_id != thin {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.map(&xml::Map {
|
||||||
|
thin_begin: m.thin_begin,
|
||||||
|
data_begin: m.data_begin,
|
||||||
|
time: 0,
|
||||||
|
len: m.len,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.device_e()?;
|
||||||
|
}
|
||||||
|
v.superblock_e()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
struct Allocator {
|
||||||
|
runs: VecDeque<Range<u64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Allocator {
|
||||||
|
fn new_shuffled(total_len: u64, run_len: Range<u64>) -> Allocator {
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
|
||||||
|
let mut b = 0u64;
|
||||||
|
while b < total_len {
|
||||||
|
let len = u64::min(
|
||||||
|
total_len - b,
|
||||||
|
thread_rng().gen_range(run_len.start, run_len.end),
|
||||||
|
);
|
||||||
|
runs.push(b..(b + len));
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
runs.shuffle(&mut thread_rng());
|
||||||
|
let runs: VecDeque<Range<u64>> = runs.iter().map(|r| r.clone()).collect();
|
||||||
|
Allocator { runs }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.runs.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc(&mut self, len: u64) -> Result<Vec<Range<u64>>> {
|
||||||
|
let mut len = len;
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
|
||||||
|
while len > 0 {
|
||||||
|
let r = self.runs.pop_front();
|
||||||
|
|
||||||
|
if r.is_none() {
|
||||||
|
return Err(anyhow!("could not allocate; out of space"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = r.unwrap();
|
||||||
|
let rlen = r.end - r.start;
|
||||||
|
if len < rlen {
|
||||||
|
runs.push(r.start..(r.start + len));
|
||||||
|
|
||||||
|
// We need to push something back.
|
||||||
|
self.runs.push_front((r.start + len)..r.end);
|
||||||
|
len = 0;
|
||||||
|
} else {
|
||||||
|
runs.push(r.start..r.end);
|
||||||
|
len -= rlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(runs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having explicitly unmapped regions makes it easier to
|
||||||
|
// apply snapshots.
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Run {
|
||||||
|
Mapped { data_begin: u64, len: u64 },
|
||||||
|
UnMapped { len: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Run {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn len(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Run::Mapped {
|
||||||
|
data_begin: _data_begin,
|
||||||
|
len,
|
||||||
|
} => *len,
|
||||||
|
Run::UnMapped { len } => *len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split(&self, n: u64) -> (Option<Run>, Option<Run>) {
|
||||||
|
if n == 0 {
|
||||||
|
return (None, Some(self.clone()));
|
||||||
|
} else {
|
||||||
|
if self.len() <= n {
|
||||||
|
return (Some(self.clone()), None);
|
||||||
|
} else {
|
||||||
|
match self {
|
||||||
|
Run::Mapped { data_begin, len } => (
|
||||||
|
Some(Run::Mapped {
|
||||||
|
data_begin: *data_begin,
|
||||||
|
len: n,
|
||||||
|
}),
|
||||||
|
Some(Run::Mapped {
|
||||||
|
data_begin: data_begin + n,
|
||||||
|
len: len - n,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Run::UnMapped { len } => (
|
||||||
|
Some(Run::UnMapped { len: n }),
|
||||||
|
Some(Run::UnMapped { len: len - n }),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ThinDev {
|
||||||
|
thin_id: u32,
|
||||||
|
dev_size: u64,
|
||||||
|
runs: Vec<Run>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThinDev {
|
||||||
|
fn emit(&self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
||||||
|
v.device_b(&xml::Device {
|
||||||
|
dev_id: self.thin_id,
|
||||||
|
mapped_blocks: self.dev_size,
|
||||||
|
transaction: 0,
|
||||||
|
creation_time: 0,
|
||||||
|
snap_time: 0,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut b = 0;
|
||||||
|
for r in &self.runs {
|
||||||
|
match r {
|
||||||
|
Run::Mapped { data_begin, len } => {
|
||||||
|
v.map(&xml::Map {
|
||||||
|
thin_begin: b,
|
||||||
|
data_begin: *data_begin,
|
||||||
|
time: 0,
|
||||||
|
len: *len,
|
||||||
|
})?;
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
Run::UnMapped { len } => {
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.device_e()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SnapRunType {
|
||||||
|
Same,
|
||||||
|
Diff,
|
||||||
|
Hole,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SnapRun(SnapRunType, u64);
|
||||||
|
|
||||||
|
fn mk_origin(thin_id: u32, total_len: u64, allocator: &mut Allocator) -> Result<ThinDev> {
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
let mut b = 0;
|
||||||
|
while b < total_len {
|
||||||
|
let len = u64::min(thread_rng().gen_range(16, 64), total_len - b);
|
||||||
|
match thread_rng().gen_range(0, 2) {
|
||||||
|
0 => {
|
||||||
|
for data in allocator.alloc(len)? {
|
||||||
|
assert!(data.end >= data.start);
|
||||||
|
runs.push(Run::Mapped {
|
||||||
|
data_begin: data.start,
|
||||||
|
len: data.end - data.start,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
runs.push(Run::UnMapped { len });
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!("bad value returned from rng"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ThinDev {
|
||||||
|
thin_id,
|
||||||
|
dev_size: total_len,
|
||||||
|
runs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_snap_mapping(
|
||||||
|
total_len: u64,
|
||||||
|
run_len: Range<u64>,
|
||||||
|
same_percent: usize,
|
||||||
|
diff_percent: usize,
|
||||||
|
) -> Vec<SnapRun> {
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
|
||||||
|
let mut b = 0u64;
|
||||||
|
while b < total_len {
|
||||||
|
let len = u64::min(
|
||||||
|
total_len - b,
|
||||||
|
thread_rng().gen_range(run_len.start, run_len.end),
|
||||||
|
);
|
||||||
|
|
||||||
|
let n = thread_rng().gen_range(0, 100);
|
||||||
|
|
||||||
|
if n < same_percent {
|
||||||
|
runs.push(SnapRun(SnapRunType::Same, len));
|
||||||
|
} else if n < diff_percent {
|
||||||
|
runs.push(SnapRun(SnapRunType::Diff, len));
|
||||||
|
} else {
|
||||||
|
runs.push(SnapRun(SnapRunType::Hole, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
b += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
runs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_runs(mut n: u64, runs: &Vec<Run>) -> (Vec<Run>, Vec<Run>) {
|
||||||
|
let mut before = Vec::new();
|
||||||
|
let mut after = Vec::new();
|
||||||
|
|
||||||
|
for r in runs {
|
||||||
|
match r.split(n) {
|
||||||
|
(Some(lhs), None) => {
|
||||||
|
before.push(lhs);
|
||||||
|
}
|
||||||
|
(Some(lhs), Some(rhs)) => {
|
||||||
|
before.push(lhs);
|
||||||
|
after.push(rhs);
|
||||||
|
}
|
||||||
|
(None, Some(rhs)) => {
|
||||||
|
after.push(rhs);
|
||||||
|
}
|
||||||
|
(None, None) => {}
|
||||||
|
}
|
||||||
|
n -= r.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
(before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_snap_runs(
|
||||||
|
origin: &Vec<Run>,
|
||||||
|
snap: &Vec<SnapRun>,
|
||||||
|
allocator: &mut Allocator,
|
||||||
|
) -> Result<Vec<Run>> {
|
||||||
|
let mut origin = origin.clone();
|
||||||
|
let mut runs = Vec::new();
|
||||||
|
|
||||||
|
for SnapRun(st, slen) in snap {
|
||||||
|
let (os, rest) = split_runs(*slen, &origin);
|
||||||
|
match st {
|
||||||
|
SnapRunType::Same => {
|
||||||
|
for o in os {
|
||||||
|
runs.push(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SnapRunType::Diff => {
|
||||||
|
for data in allocator.alloc(*slen)? {
|
||||||
|
runs.push(Run::Mapped {
|
||||||
|
data_begin: data.start,
|
||||||
|
len: data.end - data.start,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SnapRunType::Hole => {
|
||||||
|
runs.push(Run::UnMapped { len: *slen });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
origin = rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(runs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshots share mappings, not neccessarily the entire ranges.
|
||||||
|
pub struct SnapS {
|
||||||
|
pub len: u64,
|
||||||
|
pub nr_snaps: u32,
|
||||||
|
|
||||||
|
// Snaps will differ from the origin by this percentage
|
||||||
|
pub percent_change: usize,
|
||||||
|
pub old_nr_data_blocks: u64,
|
||||||
|
pub new_nr_data_blocks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SnapS {
|
||||||
|
pub fn new(len: u64, nr_snaps: u32, percent_change: usize) -> Self {
|
||||||
|
let delta = len * (nr_snaps as u64) * (percent_change as u64) / 100;
|
||||||
|
let old_nr_data_blocks = len + 3 * delta;
|
||||||
|
let new_nr_data_blocks = len + 2 * delta;
|
||||||
|
|
||||||
|
SnapS {
|
||||||
|
len,
|
||||||
|
nr_snaps,
|
||||||
|
percent_change,
|
||||||
|
old_nr_data_blocks,
|
||||||
|
new_nr_data_blocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XmlGen for SnapS {
|
||||||
|
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
||||||
|
let mut allocator = Allocator::new_shuffled(self.old_nr_data_blocks, 64..512);
|
||||||
|
let origin = mk_origin(0, self.len, &mut allocator)?;
|
||||||
|
|
||||||
|
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
||||||
|
origin.emit(v)?;
|
||||||
|
v.superblock_e()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
173
tests/thin_check.rs
Normal file
173
tests/thin_check.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use duct::{cmd, Expression};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::from_utf8;
|
||||||
|
use tempfile::{tempdir, TempDir};
|
||||||
|
use thinp::file_utils;
|
||||||
|
use thinp::version::TOOLS_VERSION;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::mk_path;
|
||||||
|
use common::xml_generator::{write_xml, FragmentedS, SingleThinS};
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
macro_rules! thin_check {
|
||||||
|
( $( $arg: expr ),* ) => {
|
||||||
|
{
|
||||||
|
use std::ffi::OsString;
|
||||||
|
let args: &[OsString] = &[$( Into::<OsString>::into($arg) ),*];
|
||||||
|
duct::cmd("bin/thin_check", args).stdout_capture().stderr_capture()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns stderr, a non zero status must be returned
|
||||||
|
fn run_fail(command: Expression) -> Result<String> {
|
||||||
|
let output = command.stderr_capture().unchecked().run()?;
|
||||||
|
assert!(!output.status.success());
|
||||||
|
Ok(from_utf8(&output.stderr[0..]).unwrap().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_valid_md(dir: &TempDir) -> Result<PathBuf> {
|
||||||
|
let xml = mk_path(dir.path(), "meta.xml");
|
||||||
|
let md = mk_path(dir.path(), "meta.bin");
|
||||||
|
|
||||||
|
let mut gen = SingleThinS::new(0, 1024, 2048, 2048);
|
||||||
|
write_xml(&xml, &mut gen)?;
|
||||||
|
|
||||||
|
let _file = file_utils::create_sized_file(&md, 4096 * 4096);
|
||||||
|
cmd!("bin/thin_restore", "-i", xml, "-o", &md).run()?;
|
||||||
|
Ok(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_corrupt_md(dir: &TempDir) -> Result<PathBuf> {
|
||||||
|
let md = mk_path(dir.path(), "meta.bin");
|
||||||
|
let _file = file_utils::create_sized_file(&md, 4096 * 4096);
|
||||||
|
Ok(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts_flag(flag: &str) -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let md = mk_valid_md(&dir)?;
|
||||||
|
thin_check!(flag, &md).run()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_v() -> Result<()> {
|
||||||
|
let stdout = thin_check!("-V").read()?;
|
||||||
|
assert_eq!(stdout, TOOLS_VERSION);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_version() -> Result<()> {
|
||||||
|
let stdout = thin_check!("--version").read()?;
|
||||||
|
assert_eq!(stdout, TOOLS_VERSION);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
const USAGE: &'static str = "Usage: thin_check [options] {device|file}\nOptions:\n {-q|--quiet}\n {-h|--help}\n {-V|--version}\n {-m|--metadata-snap}\n {--override-mapping-root}\n {--clear-needs-check-flag}\n {--ignore-non-fatal-errors}\n {--skip-mappings}\n {--super-block-only}";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_h() -> Result<()> {
|
||||||
|
let stdout = thin_check!("-h").read()?;
|
||||||
|
assert_eq!(stdout, USAGE);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_help() -> Result<()> {
|
||||||
|
let stdout = thin_check!("--help").read()?;
|
||||||
|
assert_eq!(stdout, USAGE);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_bad_option() -> Result<()> {
|
||||||
|
let stderr = run_fail(thin_check!("--hedgehogs-only"))?;
|
||||||
|
assert!(stderr.contains("unrecognized option \'--hedgehogs-only\'"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_superblock_only() -> Result<()> {
|
||||||
|
accepts_flag("--super-block-only")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_skip_mappings() -> Result<()> {
|
||||||
|
accepts_flag("--skip-mappings")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_ignore_non_fatal_errors() -> Result<()> {
|
||||||
|
accepts_flag("--ignore-non-fatal-errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_clear_needs_check_flag() -> Result<()> {
|
||||||
|
accepts_flag("--clear-needs-check-flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_quiet() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let md = mk_valid_md(&dir)?;
|
||||||
|
|
||||||
|
let output = thin_check!("--quiet", &md).run()?;
|
||||||
|
assert_eq!(output.stdout.len(), 0);
|
||||||
|
assert_eq!(output.stderr.len(), 0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detects_corrupt_superblock_with_superblock_only() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let md = mk_corrupt_md(&dir)?;
|
||||||
|
let output = thin_check!("--super-block-only", &md).unchecked().run()?;
|
||||||
|
assert!(!output.status.success());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_help_message_for_tiny_metadata() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let md = mk_path(dir.path(), "meta.bin");
|
||||||
|
let _file = file_utils::create_sized_file(&md, 1024);
|
||||||
|
let stderr = run_fail(thin_check!(&md))?;
|
||||||
|
assert!(stderr.contains("Metadata device/file too small. Is this binary metadata?"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spot_xml_data() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let xml = mk_path(dir.path(), "meta.xml");
|
||||||
|
|
||||||
|
let mut gen = FragmentedS::new(4, 10240);
|
||||||
|
write_xml(&xml, &mut gen)?;
|
||||||
|
|
||||||
|
let stderr = run_fail(thin_check!(&xml))?;
|
||||||
|
eprintln!("{}", stderr);
|
||||||
|
assert!(
|
||||||
|
stderr.contains("This looks like XML. thin_check only checks the binary metadata format.")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_info_fields() -> Result<()> {
|
||||||
|
let dir = tempdir()?;
|
||||||
|
let md = mk_valid_md(&dir)?;
|
||||||
|
let stdout = thin_check!(&md).read()?;
|
||||||
|
assert!(stdout.contains("TRANSACTION_ID="));
|
||||||
|
assert!(stdout.contains("METADATA_FREE_BLOCKS="));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
@ -1,16 +1,20 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||||
use std::ops::Range;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use thinp::file_utils;
|
use thinp::file_utils;
|
||||||
use thinp::thin::xml::{self, Visit};
|
use thinp::thin::xml::{self, Visit};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::mk_path;
|
||||||
|
use common::xml_generator::{
|
||||||
|
write_xml, EmptyPoolS, FragmentedS, SingleThinS, SnapS, XmlGen,
|
||||||
|
};
|
||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -251,25 +255,6 @@ impl<'a, R: Read + Seek> ThinVisitor for Verifier<'a, R> {
|
|||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
fn mk_path(dir: &Path, file: &str) -> PathBuf {
|
|
||||||
let mut p = PathBuf::new();
|
|
||||||
p.push(dir);
|
|
||||||
p.push(PathBuf::from(file));
|
|
||||||
p
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_xml(path: &Path, g: &mut dyn Scenario) -> Result<()> {
|
|
||||||
let xml_out = OpenOptions::new()
|
|
||||||
.read(false)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(path)?;
|
|
||||||
let mut w = xml::XmlWriter::new(xml_out);
|
|
||||||
|
|
||||||
g.generate_xml(&mut w)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_data_file(data_path: &Path, xml_path: &Path) -> Result<()> {
|
fn create_data_file(data_path: &Path, xml_path: &Path) -> Result<()> {
|
||||||
let input = OpenOptions::new().read(true).write(false).open(xml_path)?;
|
let input = OpenOptions::new().read(true).write(false).open(xml_path)?;
|
||||||
|
|
||||||
@ -304,17 +289,19 @@ fn verify(xml_path: &Path, data_path: &Path, seed: u64) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trait Scenario {
|
trait Scenario {
|
||||||
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()>;
|
|
||||||
fn get_new_nr_blocks(&self) -> u64;
|
fn get_new_nr_blocks(&self) -> u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_shrink(scenario: &mut dyn Scenario) -> Result<()> {
|
fn test_shrink<S>(scenario: &mut S) -> Result<()>
|
||||||
|
where
|
||||||
|
S: Scenario + XmlGen,
|
||||||
|
{
|
||||||
let dir = tempdir()?;
|
let dir = tempdir()?;
|
||||||
let xml_before = mk_path(dir.path(), "before.xml");
|
let xml_before = mk_path(dir.path(), "before.xml");
|
||||||
let xml_after = mk_path(dir.path(), "after.xml");
|
let xml_after = mk_path(dir.path(), "after.xml");
|
||||||
let data_path = mk_path(dir.path(), "metadata.bin");
|
let data_path = mk_path(dir.path(), "metadata.bin");
|
||||||
|
|
||||||
generate_xml(&xml_before, scenario)?;
|
write_xml(&xml_before, scenario)?;
|
||||||
create_data_file(&data_path, &xml_before)?;
|
create_data_file(&data_path, &xml_before)?;
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@ -332,28 +319,7 @@ fn test_shrink(scenario: &mut dyn Scenario) -> Result<()> {
|
|||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
fn common_sb(nr_blocks: u64) -> xml::Superblock {
|
|
||||||
xml::Superblock {
|
|
||||||
uuid: "".to_string(),
|
|
||||||
time: 0,
|
|
||||||
transaction: 0,
|
|
||||||
flags: None,
|
|
||||||
version: None,
|
|
||||||
data_block_size: 32,
|
|
||||||
nr_data_blocks: nr_blocks,
|
|
||||||
metadata_snap: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmptyPoolS {}
|
|
||||||
|
|
||||||
impl Scenario for EmptyPoolS {
|
impl Scenario for EmptyPoolS {
|
||||||
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
|
||||||
v.superblock_b(&common_sb(1024))?;
|
|
||||||
v.superblock_e()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_new_nr_blocks(&self) -> u64 {
|
fn get_new_nr_blocks(&self) -> u64 {
|
||||||
512
|
512
|
||||||
}
|
}
|
||||||
@ -367,45 +333,7 @@ fn shrink_empty_pool() -> Result<()> {
|
|||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
struct SingleThinS {
|
|
||||||
offset: u64,
|
|
||||||
len: u64,
|
|
||||||
old_nr_data_blocks: u64,
|
|
||||||
new_nr_data_blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SingleThinS {
|
|
||||||
fn new(offset: u64, len: u64, old_nr_data_blocks: u64, new_nr_data_blocks: u64) -> Self {
|
|
||||||
SingleThinS {
|
|
||||||
offset,
|
|
||||||
len,
|
|
||||||
old_nr_data_blocks,
|
|
||||||
new_nr_data_blocks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scenario for SingleThinS {
|
impl Scenario for SingleThinS {
|
||||||
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
|
||||||
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
|
||||||
v.device_b(&xml::Device {
|
|
||||||
dev_id: 0,
|
|
||||||
mapped_blocks: self.len,
|
|
||||||
transaction: 0,
|
|
||||||
creation_time: 0,
|
|
||||||
snap_time: 0,
|
|
||||||
})?;
|
|
||||||
v.map(&xml::Map {
|
|
||||||
thin_begin: 0,
|
|
||||||
data_begin: self.offset,
|
|
||||||
time: 0,
|
|
||||||
len: self.len,
|
|
||||||
})?;
|
|
||||||
v.device_e()?;
|
|
||||||
v.superblock_e()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_new_nr_blocks(&self) -> u64 {
|
fn get_new_nr_blocks(&self) -> u64 {
|
||||||
self.new_nr_data_blocks
|
self.new_nr_data_blocks
|
||||||
}
|
}
|
||||||
@ -452,128 +380,7 @@ fn shrink_insufficient_space() -> Result<()> {
|
|||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
struct FragmentedS {
|
|
||||||
nr_thins: u32,
|
|
||||||
thin_size: u64,
|
|
||||||
old_nr_data_blocks: u64,
|
|
||||||
new_nr_data_blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FragmentedS {
|
|
||||||
fn new(nr_thins: u32, thin_size: u64) -> Self {
|
|
||||||
let old_size = (nr_thins as u64) * thin_size;
|
|
||||||
FragmentedS {
|
|
||||||
nr_thins,
|
|
||||||
thin_size,
|
|
||||||
old_nr_data_blocks: (nr_thins as u64) * thin_size,
|
|
||||||
new_nr_data_blocks: old_size * 3 / 4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ThinRun {
|
|
||||||
thin_id: u32,
|
|
||||||
thin_begin: u64,
|
|
||||||
len: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Copy)]
|
|
||||||
struct MappedRun {
|
|
||||||
thin_id: u32,
|
|
||||||
thin_begin: u64,
|
|
||||||
data_begin: u64,
|
|
||||||
len: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mk_runs(thin_id: u32, total_len: u64, run_len: std::ops::Range<u64>) -> Vec<ThinRun> {
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
let mut b = 0u64;
|
|
||||||
while b < total_len {
|
|
||||||
let len = u64::min(
|
|
||||||
total_len - b,
|
|
||||||
thread_rng().gen_range(run_len.start, run_len.end),
|
|
||||||
);
|
|
||||||
runs.push(ThinRun {
|
|
||||||
thin_id: thin_id,
|
|
||||||
thin_begin: b,
|
|
||||||
len,
|
|
||||||
});
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
runs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scenario for FragmentedS {
|
impl Scenario for FragmentedS {
|
||||||
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
|
||||||
// Allocate each thin fully, in runs between 1 and 16.
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
for thin in 0..self.nr_thins {
|
|
||||||
runs.append(&mut mk_runs(thin, self.thin_size, 1..17));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle
|
|
||||||
runs.shuffle(&mut rand::thread_rng());
|
|
||||||
|
|
||||||
// map across the data
|
|
||||||
let mut maps = Vec::new();
|
|
||||||
let mut b = 0;
|
|
||||||
for r in &runs {
|
|
||||||
maps.push(MappedRun {
|
|
||||||
thin_id: r.thin_id,
|
|
||||||
thin_begin: r.thin_begin,
|
|
||||||
data_begin: b,
|
|
||||||
len: r.len,
|
|
||||||
});
|
|
||||||
b += r.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop half the mappings, which leaves us free runs
|
|
||||||
let mut dropped = Vec::new();
|
|
||||||
for i in 0..maps.len() {
|
|
||||||
if i % 2 == 0 {
|
|
||||||
dropped.push(maps[i].clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unshuffle. This isn't strictly necc. but makes the xml
|
|
||||||
// more readable.
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
maps.sort_by(|&l, &r| match l.thin_id.cmp(&r.thin_id) {
|
|
||||||
Ordering::Equal => l.thin_begin.cmp(&r.thin_begin),
|
|
||||||
o => o,
|
|
||||||
});
|
|
||||||
|
|
||||||
// write the xml
|
|
||||||
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
|
||||||
for thin in 0..self.nr_thins {
|
|
||||||
v.device_b(&xml::Device {
|
|
||||||
dev_id: thin,
|
|
||||||
mapped_blocks: self.thin_size,
|
|
||||||
transaction: 0,
|
|
||||||
creation_time: 0,
|
|
||||||
snap_time: 0,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for m in &dropped {
|
|
||||||
if m.thin_id != thin {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
v.map(&xml::Map {
|
|
||||||
thin_begin: m.thin_begin,
|
|
||||||
data_begin: m.data_begin,
|
|
||||||
time: 0,
|
|
||||||
len: m.len,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
v.device_e()?;
|
|
||||||
}
|
|
||||||
v.superblock_e()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_new_nr_blocks(&self) -> u64 {
|
fn get_new_nr_blocks(&self) -> u64 {
|
||||||
self.new_nr_data_blocks
|
self.new_nr_data_blocks
|
||||||
}
|
}
|
||||||
@ -605,321 +412,7 @@ fn shrink_fragmented_thin_64() -> Result<()> {
|
|||||||
|
|
||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
struct Allocator {
|
|
||||||
runs: VecDeque<Range<u64>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Allocator {
|
|
||||||
fn new_shuffled(total_len: u64, run_len: Range<u64>) -> Allocator {
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
|
|
||||||
let mut b = 0u64;
|
|
||||||
while b < total_len {
|
|
||||||
let len = u64::min(
|
|
||||||
total_len - b,
|
|
||||||
thread_rng().gen_range(run_len.start, run_len.end),
|
|
||||||
);
|
|
||||||
runs.push(b..(b + len));
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
runs.shuffle(&mut thread_rng());
|
|
||||||
let runs: VecDeque<Range<u64>> = runs.iter().map(|r| r.clone()).collect();
|
|
||||||
Allocator { runs }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.runs.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc(&mut self, len: u64) -> Result<Vec<Range<u64>>> {
|
|
||||||
let mut len = len;
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
|
|
||||||
while len > 0 {
|
|
||||||
let r = self.runs.pop_front();
|
|
||||||
|
|
||||||
if r.is_none() {
|
|
||||||
return Err(anyhow!("could not allocate; out of space"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut r = r.unwrap();
|
|
||||||
let rlen = r.end - r.start;
|
|
||||||
if len < rlen {
|
|
||||||
runs.push(r.start..(r.start + len));
|
|
||||||
|
|
||||||
// We need to push something back.
|
|
||||||
self.runs.push_front((r.start + len)..r.end);
|
|
||||||
len = 0;
|
|
||||||
} else {
|
|
||||||
runs.push(r.start..r.end);
|
|
||||||
len -= rlen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(runs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Having explicitly unmapped regions makes it easier to
|
|
||||||
// apply snapshots.
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum Run {
|
|
||||||
Mapped { data_begin: u64, len: u64 },
|
|
||||||
UnMapped { len: u64 },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Run {
|
|
||||||
fn len(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
Run::Mapped {
|
|
||||||
data_begin: _data_begin,
|
|
||||||
len,
|
|
||||||
} => *len,
|
|
||||||
Run::UnMapped { len } => *len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split(&self, n: u64) -> (Option<Run>, Option<Run>) {
|
|
||||||
if n == 0 {
|
|
||||||
return (None, Some(self.clone()));
|
|
||||||
} else {
|
|
||||||
if self.len() <= n {
|
|
||||||
return (Some(self.clone()), None);
|
|
||||||
} else {
|
|
||||||
match self {
|
|
||||||
Run::Mapped { data_begin, len } => (
|
|
||||||
Some(Run::Mapped {
|
|
||||||
data_begin: *data_begin,
|
|
||||||
len: n,
|
|
||||||
}),
|
|
||||||
Some(Run::Mapped {
|
|
||||||
data_begin: data_begin + n,
|
|
||||||
len: len - n,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Run::UnMapped { len } => (
|
|
||||||
Some(Run::UnMapped { len: n }),
|
|
||||||
Some(Run::UnMapped { len: len - n }),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ThinDev {
|
|
||||||
thin_id: u32,
|
|
||||||
dev_size: u64,
|
|
||||||
runs: Vec<Run>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThinDev {
|
|
||||||
fn emit(&self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
|
||||||
v.device_b(&xml::Device {
|
|
||||||
dev_id: self.thin_id,
|
|
||||||
mapped_blocks: self.dev_size,
|
|
||||||
transaction: 0,
|
|
||||||
creation_time: 0,
|
|
||||||
snap_time: 0,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut b = 0;
|
|
||||||
for r in &self.runs {
|
|
||||||
match r {
|
|
||||||
Run::Mapped { data_begin, len } => {
|
|
||||||
v.map(&xml::Map {
|
|
||||||
thin_begin: b,
|
|
||||||
data_begin: *data_begin,
|
|
||||||
time: 0,
|
|
||||||
len: *len,
|
|
||||||
})?;
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
Run::UnMapped { len } => {
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v.device_e()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum SnapRunType {
|
|
||||||
Same,
|
|
||||||
Diff,
|
|
||||||
Hole,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct SnapRun(SnapRunType, u64);
|
|
||||||
|
|
||||||
fn mk_origin(thin_id: u32, total_len: u64, allocator: &mut Allocator) -> Result<ThinDev> {
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
let mut b = 0;
|
|
||||||
while b < total_len {
|
|
||||||
let len = u64::min(thread_rng().gen_range(16, 64), total_len - b);
|
|
||||||
match thread_rng().gen_range(0, 2) {
|
|
||||||
0 => {
|
|
||||||
for data in allocator.alloc(len)? {
|
|
||||||
assert!(data.end >= data.start);
|
|
||||||
runs.push(Run::Mapped {
|
|
||||||
data_begin: data.start,
|
|
||||||
len: data.end - data.start,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
runs.push(Run::UnMapped { len });
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow!("bad value returned from rng"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ThinDev {
|
|
||||||
thin_id,
|
|
||||||
dev_size: total_len,
|
|
||||||
runs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mk_snap_mapping(
|
|
||||||
total_len: u64,
|
|
||||||
run_len: Range<u64>,
|
|
||||||
same_percent: usize,
|
|
||||||
diff_percent: usize,
|
|
||||||
) -> Vec<SnapRun> {
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
|
|
||||||
let mut b = 0u64;
|
|
||||||
while b < total_len {
|
|
||||||
let len = u64::min(
|
|
||||||
total_len - b,
|
|
||||||
thread_rng().gen_range(run_len.start, run_len.end),
|
|
||||||
);
|
|
||||||
|
|
||||||
let n = thread_rng().gen_range(0, 100);
|
|
||||||
|
|
||||||
if n < same_percent {
|
|
||||||
runs.push(SnapRun(SnapRunType::Same, len));
|
|
||||||
} else if n < diff_percent {
|
|
||||||
runs.push(SnapRun(SnapRunType::Diff, len));
|
|
||||||
} else {
|
|
||||||
runs.push(SnapRun(SnapRunType::Hole, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
b += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
runs
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_runs(mut n: u64, runs: &Vec<Run>) -> (Vec<Run>, Vec<Run>) {
|
|
||||||
let mut before = Vec::new();
|
|
||||||
let mut after = Vec::new();
|
|
||||||
|
|
||||||
for r in runs {
|
|
||||||
match r.split(n) {
|
|
||||||
(Some(lhs), None) => {
|
|
||||||
before.push(lhs);
|
|
||||||
}
|
|
||||||
(Some(lhs), Some(rhs)) => {
|
|
||||||
before.push(lhs);
|
|
||||||
after.push(rhs);
|
|
||||||
}
|
|
||||||
(None, Some(rhs)) => {
|
|
||||||
after.push(rhs);
|
|
||||||
}
|
|
||||||
(None, None) => {}
|
|
||||||
}
|
|
||||||
n -= r.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
(before, after)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_snap_runs(
|
|
||||||
origin: &Vec<Run>,
|
|
||||||
snap: &Vec<SnapRun>,
|
|
||||||
allocator: &mut Allocator,
|
|
||||||
) -> Result<Vec<Run>> {
|
|
||||||
let mut origin = origin.clone();
|
|
||||||
let mut runs = Vec::new();
|
|
||||||
|
|
||||||
for SnapRun(st, slen) in snap {
|
|
||||||
let (os, rest) = split_runs(*slen, &origin);
|
|
||||||
match st {
|
|
||||||
SnapRunType::Same => {
|
|
||||||
for o in os {
|
|
||||||
runs.push(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SnapRunType::Diff => {
|
|
||||||
for data in allocator.alloc(*slen)? {
|
|
||||||
runs.push(Run::Mapped {
|
|
||||||
data_begin: data.start,
|
|
||||||
len: data.end - data.start,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SnapRunType::Hole => {
|
|
||||||
runs.push(Run::UnMapped { len: *slen });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
origin = rest;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(runs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshots share mappings, not neccessarily the entire ranges.
|
|
||||||
struct SnapS {
|
|
||||||
len: u64,
|
|
||||||
nr_snaps: u32,
|
|
||||||
|
|
||||||
// Snaps will differ from the origin by this percentage
|
|
||||||
percent_change: usize,
|
|
||||||
old_nr_data_blocks: u64,
|
|
||||||
new_nr_data_blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SnapS {
|
|
||||||
fn new(len: u64, nr_snaps: u32, percent_change: usize) -> Self {
|
|
||||||
let delta = len * (nr_snaps as u64) * (percent_change as u64) / 100;
|
|
||||||
let old_nr_data_blocks = len + 3 * delta;
|
|
||||||
let new_nr_data_blocks = len + 2 * delta;
|
|
||||||
|
|
||||||
SnapS {
|
|
||||||
len,
|
|
||||||
nr_snaps,
|
|
||||||
percent_change,
|
|
||||||
old_nr_data_blocks,
|
|
||||||
new_nr_data_blocks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scenario for SnapS {
|
impl Scenario for SnapS {
|
||||||
fn generate_xml(&mut self, v: &mut dyn xml::MetadataVisitor) -> Result<()> {
|
|
||||||
let mut allocator = Allocator::new_shuffled(self.old_nr_data_blocks, 64..512);
|
|
||||||
let origin = mk_origin(0, self.len, &mut allocator)?;
|
|
||||||
|
|
||||||
v.superblock_b(&common_sb(self.old_nr_data_blocks))?;
|
|
||||||
origin.emit(v)?;
|
|
||||||
v.superblock_e()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_new_nr_blocks(&self) -> u64 {
|
fn get_new_nr_blocks(&self) -> u64 {
|
||||||
self.new_nr_data_blocks
|
self.new_nr_data_blocks
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user