Decompose oversized Rust files by responsibility
This commit is contained in:
parent
03bfe92ca0
commit
ee9201b057
1 changed files with 18648 additions and 18513 deletions
|
|
@ -472,806 +472,5 @@ fn upper_entries() -> Vec<Entry<'static>> {
|
|||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_corpus_preserves_supported_changesets_and_read_only_lower_use() {
|
||||
let fixture = Fixture::new();
|
||||
let first = layer(&lower_entries());
|
||||
let second = layer(&upper_entries());
|
||||
let request = fixture.request(
|
||||
1,
|
||||
&[
|
||||
(compress(&first, OCI_GZIP), OCI_GZIP),
|
||||
(compress(&second, OCI_ZSTD), OCI_ZSTD),
|
||||
],
|
||||
);
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
let root = fixture.artifact_root(&artifact);
|
||||
assert!(!root.join("gone").exists());
|
||||
assert!(!root.join("opaque/old").exists());
|
||||
assert_eq!(std::fs::read(root.join("opaque/new")).unwrap(), b"new");
|
||||
assert_eq!(
|
||||
std::fs::read(root.join("directory-to-file")).unwrap(),
|
||||
b"replacement"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read(root.join("file-to-directory/new")).unwrap(),
|
||||
b"new"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_link(root.join("symbolic")).unwrap(),
|
||||
Path::new("target")
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::metadata(root.join("hard")).unwrap().ino(),
|
||||
std::fs::metadata(root.join("target")).unwrap().ino()
|
||||
);
|
||||
assert_eq!(std::fs::read(root.join("forward")).unwrap(), b"forward");
|
||||
assert_eq!(
|
||||
std::fs::metadata(root.join("later"))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o7777,
|
||||
0o4755
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&request.layout_path).unwrap();
|
||||
let mut lower = File::open(root.join("directory-to-file")).unwrap();
|
||||
let mut bytes = Vec::new();
|
||||
lower.read_to_end(&mut bytes).unwrap();
|
||||
assert_eq!(bytes, b"replacement");
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("stages")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_manifest_identity_must_match_verified_layout_manifest() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"content",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let mut request = fixture.request(3, &[(bytes, OCI_TAR)]);
|
||||
request.selected_manifest_digest = [0xff; 32];
|
||||
|
||||
assert!(fixture.store.prepare(&request).is_err());
|
||||
let snapshot = fixture.catalog.snapshot().unwrap();
|
||||
assert!(snapshot.prepared_artifacts.is_empty());
|
||||
assert!(snapshot.prepare_operations.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_entry_matching_internal_spool_name_is_preserved() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: ".layer-0.tar",
|
||||
kind: EntryType::Regular,
|
||||
data: b"image-owned",
|
||||
target: None,
|
||||
mode: 0o640,
|
||||
}]);
|
||||
let request = fixture.request(4, &[(bytes, OCI_TAR)]);
|
||||
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
assert_eq!(
|
||||
std::fs::read(fixture.artifact_root(&artifact).join(".layer-0.tar")).unwrap(),
|
||||
b"image-owned"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_directory_layer_entry_is_applied() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[
|
||||
Entry {
|
||||
path: ".",
|
||||
kind: EntryType::Directory,
|
||||
data: b"",
|
||||
target: None,
|
||||
mode: 0o755,
|
||||
},
|
||||
Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"content",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
},
|
||||
]);
|
||||
let request = fixture.request(5, &[(bytes, OCI_TAR)]);
|
||||
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
let root = fixture.artifact_root(&artifact);
|
||||
assert_eq!(std::fs::read(root.join("payload")).unwrap(), b"content");
|
||||
assert_eq!(
|
||||
root.metadata().unwrap().permissions().mode() & 0o7777,
|
||||
0o755
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn container_root_owner_maps_to_invoking_host_ids() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer_with_owner(
|
||||
&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"content",
|
||||
target: None,
|
||||
mode: 0o640,
|
||||
}],
|
||||
0,
|
||||
0,
|
||||
);
|
||||
let mut request = fixture.request(6, &[(bytes, OCI_TAR)]);
|
||||
request.id_map = IdMap {
|
||||
uid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::geteuid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
gid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::getegid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
};
|
||||
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
let metadata = fixture
|
||||
.artifact_root(&artifact)
|
||||
.join("payload")
|
||||
.metadata()
|
||||
.unwrap();
|
||||
assert_eq!(metadata.uid(), rustix::process::geteuid().as_raw());
|
||||
assert_eq!(metadata.gid(), rustix::process::getegid().as_raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmapped_nonzero_owners_refuse_before_publication() {
|
||||
for (operation, uid, gid) in [(7, 1, 0), (8, 0, 1)] {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer_with_owner(
|
||||
&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"content",
|
||||
target: None,
|
||||
mode: 0o640,
|
||||
}],
|
||||
uid,
|
||||
gid,
|
||||
);
|
||||
let mut request = fixture.request(operation, &[(bytes, OCI_TAR)]);
|
||||
request.id_map = IdMap {
|
||||
uid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::geteuid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
gid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::getegid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
};
|
||||
|
||||
assert!(fixture.store.prepare(&request).is_err());
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.prepared_artifacts
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_opaque_whiteout_removes_only_lower_layer_entries() {
|
||||
let fixture = Fixture::new();
|
||||
let lower = layer(&[Entry {
|
||||
path: "old",
|
||||
kind: EntryType::Regular,
|
||||
data: b"old",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let upper = layer(&[
|
||||
Entry {
|
||||
path: "new",
|
||||
kind: EntryType::Regular,
|
||||
data: b"new",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
},
|
||||
Entry {
|
||||
path: ".wh..wh..opq",
|
||||
kind: EntryType::Regular,
|
||||
data: b"",
|
||||
target: None,
|
||||
mode: 0o000,
|
||||
},
|
||||
]);
|
||||
let request = fixture.request(2, &[(lower, OCI_TAR), (upper, OCI_TAR)]);
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
let root = fixture.artifact_root(&artifact);
|
||||
assert!(!root.join("old").exists());
|
||||
assert_eq!(std::fs::read(root.join("new")).unwrap(), b"new");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_and_corrupt_layers_refuse_without_publication() {
|
||||
let cases = [
|
||||
layer(&[Entry {
|
||||
path: "pipe",
|
||||
kind: EntryType::Fifo,
|
||||
data: b"",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]),
|
||||
layer(&[Entry {
|
||||
path: "escape",
|
||||
kind: EntryType::Link,
|
||||
data: b"",
|
||||
target: Some("../outside"),
|
||||
mode: 0o644,
|
||||
}]),
|
||||
xattr_layer(),
|
||||
];
|
||||
for (index, bytes) in cases.into_iter().enumerate() {
|
||||
let fixture = Fixture::new();
|
||||
let request = fixture.request(u8::try_from(index).unwrap() + 10, &[(bytes, OCI_TAR)]);
|
||||
assert!(fixture.store.prepare(&request).is_err());
|
||||
let snapshot = fixture.catalog.snapshot().unwrap();
|
||||
assert!(snapshot.prepared_artifacts.is_empty());
|
||||
assert!(snapshot.prepare_operations.is_empty());
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("stages")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "file",
|
||||
kind: EntryType::Regular,
|
||||
data: b"content",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let request = fixture.request(20, &[(bytes, OCI_TAR)]);
|
||||
let blob = request
|
||||
.layout_path
|
||||
.join("blobs/sha256")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.map(|entry| entry.unwrap().path())
|
||||
.find(|path| std::fs::metadata(path).unwrap().len() > 1024)
|
||||
.unwrap();
|
||||
OpenOptionsExt::append_byte(&blob);
|
||||
assert!(fixture.store.prepare(&request).is_err());
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.prepared_artifacts
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
struct OpenOptionsExt;
|
||||
|
||||
impl OpenOptionsExt {
|
||||
fn append_byte(path: &Path) {
|
||||
let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap();
|
||||
file.write_all(b"x").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_expansion_layer_is_disk_spooled_and_published() {
|
||||
let fixture = Fixture::new();
|
||||
let payload = vec![0u8; 32 * 1024 * 1024];
|
||||
let tar = layer(&[Entry {
|
||||
path: "expanded",
|
||||
kind: EntryType::Regular,
|
||||
data: &payload,
|
||||
target: None,
|
||||
mode: 0o600,
|
||||
}]);
|
||||
let compressed = compress(&tar, OCI_GZIP);
|
||||
assert!(compressed.len() * 100 < payload.len());
|
||||
let request = fixture.request(30, &[(compressed, OCI_GZIP)]);
|
||||
let artifact = fixture.store.prepare(&request).unwrap();
|
||||
assert_eq!(
|
||||
std::fs::metadata(fixture.artifact_root(&artifact).join("expanded"))
|
||||
.unwrap()
|
||||
.len(),
|
||||
payload.len() as u64
|
||||
);
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("stages")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_publication_reuses_one_immutable_artifact() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let first = fixture.request(40, &[(bytes.clone(), OCI_TAR)]);
|
||||
let mut second = PrepareRequest {
|
||||
operation_id: PrepareOperationId::new([41; 32]),
|
||||
..first.clone()
|
||||
};
|
||||
second.layout_path = first.layout_path.clone();
|
||||
let store = Arc::new(fixture.store.clone());
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
let first_handle = {
|
||||
let store = Arc::clone(&store);
|
||||
let barrier = Arc::clone(&barrier);
|
||||
thread::spawn(move || {
|
||||
barrier.wait();
|
||||
store.prepare(&first)
|
||||
})
|
||||
};
|
||||
let second_handle = {
|
||||
let store = Arc::clone(&store);
|
||||
let barrier = Arc::clone(&barrier);
|
||||
thread::spawn(move || {
|
||||
barrier.wait();
|
||||
store.prepare(&second)
|
||||
})
|
||||
};
|
||||
barrier.wait();
|
||||
let first = first_handle.join().unwrap().unwrap();
|
||||
let second = second_handle.join().unwrap().unwrap();
|
||||
assert_eq!(first, second);
|
||||
let snapshot = fixture.catalog.snapshot().unwrap();
|
||||
assert_eq!(snapshot.prepared_artifacts, vec![first]);
|
||||
assert!(snapshot.prepare_operations.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_during_reclaim_preserves_replacement_and_deletes_old_inode() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let first_request = fixture.request(50, &[(bytes, OCI_TAR)]);
|
||||
let first = fixture.store.prepare(&first_request).unwrap();
|
||||
let reclaiming = fixture.catalog.begin_reclaim(&first).unwrap();
|
||||
let replacement_request = PrepareRequest {
|
||||
operation_id: PrepareOperationId::new([51; 32]),
|
||||
..first_request
|
||||
};
|
||||
let barrier = Arc::new(Barrier::new(3));
|
||||
let store = Arc::new(fixture.store.clone());
|
||||
let reclaim_handle = {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let store = Arc::clone(&store);
|
||||
thread::spawn(move || {
|
||||
barrier.wait();
|
||||
store.resume_reclaim(&reclaiming)
|
||||
})
|
||||
};
|
||||
let publish_handle = {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let store = Arc::clone(&store);
|
||||
thread::spawn(move || {
|
||||
barrier.wait();
|
||||
store.prepare(&replacement_request)
|
||||
})
|
||||
};
|
||||
barrier.wait();
|
||||
let reclaim = reclaim_handle.join().unwrap().unwrap();
|
||||
let replacement = publish_handle.join().unwrap().unwrap();
|
||||
assert!(matches!(
|
||||
reclaim,
|
||||
ReclaimOutcome::Removed | ReclaimOutcome::ReplacementPreserved
|
||||
));
|
||||
assert_ne!(replacement.inode, first.inode);
|
||||
assert_eq!(
|
||||
fixture.catalog.snapshot().unwrap().prepared_artifacts,
|
||||
vec![replacement]
|
||||
);
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("quarantine")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacement_winning_before_reclaimer_resumes_still_cleans_old_quarantine() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let first_request = fixture.request(52, &[(bytes, OCI_TAR)]);
|
||||
let first = fixture.store.prepare(&first_request).unwrap();
|
||||
let reclaiming = fixture.catalog.begin_reclaim(&first).unwrap();
|
||||
let replacement_request = PrepareRequest {
|
||||
operation_id: PrepareOperationId::new([53; 32]),
|
||||
..first_request
|
||||
};
|
||||
|
||||
let replacement = fixture.store.prepare(&replacement_request).unwrap();
|
||||
assert_ne!(replacement.inode, first.inode);
|
||||
assert_eq!(
|
||||
fixture.store.resume_reclaim(&reclaiming).unwrap(),
|
||||
ReclaimOutcome::ReplacementPreserved
|
||||
);
|
||||
assert_eq!(
|
||||
fixture.catalog.snapshot().unwrap().prepared_artifacts,
|
||||
vec![replacement.clone()]
|
||||
);
|
||||
assert!(fixture.artifact_root(&replacement).exists());
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("quarantine")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacement_reclaim_start_does_not_orphan_prior_quarantine() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let first_request = fixture.request(55, &[(bytes, OCI_TAR)]);
|
||||
let first = fixture.store.prepare(&first_request).unwrap();
|
||||
let first_reclaiming = fixture.catalog.begin_reclaim(&first).unwrap();
|
||||
let replacement_request = PrepareRequest {
|
||||
operation_id: PrepareOperationId::new([56; 32]),
|
||||
..first_request
|
||||
};
|
||||
let replacement = fixture.store.prepare(&replacement_request).unwrap();
|
||||
let replacement_reclaiming = fixture.catalog.begin_reclaim(&replacement).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fixture.store.resume_reclaim(&first_reclaiming).unwrap(),
|
||||
ReclaimOutcome::ReplacementPreserved
|
||||
);
|
||||
assert!(fixture.artifact_root(&replacement).is_dir());
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("quarantine")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(
|
||||
fixture
|
||||
.store
|
||||
.resume_reclaim(&replacement_reclaiming)
|
||||
.unwrap(),
|
||||
ReclaimOutcome::Removed
|
||||
);
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.prepared_artifacts
|
||||
.is_empty()
|
||||
);
|
||||
assert!(!fixture.artifact_root(&replacement).exists());
|
||||
assert!(
|
||||
fixture
|
||||
.root
|
||||
.join("quarantine")
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.next()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reclaim_refuses_preexisting_foreign_quarantine_path() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let artifact = fixture
|
||||
.store
|
||||
.prepare(&fixture.request(54, &[(bytes, OCI_TAR)]))
|
||||
.unwrap();
|
||||
let foreign = fixture.root.join("quarantine").join(format!(
|
||||
"{}-{}-{}",
|
||||
hex(artifact.prepared_artifact_id.as_bytes()),
|
||||
artifact.device.get(),
|
||||
artifact.inode.get()
|
||||
));
|
||||
std::fs::create_dir(&foreign).unwrap();
|
||||
|
||||
assert!(fixture.store.reclaim(&artifact).is_err());
|
||||
assert!(foreign.is_dir());
|
||||
assert!(fixture.artifact_root(&artifact).is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovery_deletes_only_exact_owned_stage_and_preserves_foreign_stage() {
|
||||
let fixture = Fixture::new();
|
||||
let operation_id = PrepareOperationId::new([60; 32]);
|
||||
let stage = Path::new("stages").join(hex(operation_id.as_bytes()));
|
||||
let intent = PrepareOperation {
|
||||
operation_id,
|
||||
expected_artifact_id: PreparedArtifactId::new([61; 32]),
|
||||
source_identity: SourceIdentity::new(b"source".to_vec()),
|
||||
selected_manifest_digest: SelectedManifestDigest::new([62; 32]),
|
||||
policy_id: PolicyId::new([63; 32]),
|
||||
id_map_digest: IdMapDigest::new([64; 32]),
|
||||
stage_path: HostPath::new(stage.as_os_str().as_encoded_bytes().to_vec()),
|
||||
stage_state: PrepareStageState::Intent,
|
||||
device: None,
|
||||
inode: None,
|
||||
};
|
||||
fixture.catalog.record_prepare(&intent).unwrap();
|
||||
std::fs::create_dir(fixture.root.join(&stage)).unwrap();
|
||||
std::fs::write(fixture.root.join(&stage).join("foreign"), b"keep").unwrap();
|
||||
assert!(fixture.store.recover_prepare(&intent).is_err());
|
||||
assert!(fixture.root.join(&stage).join("foreign").exists());
|
||||
std::fs::remove_dir_all(fixture.root.join(&stage)).unwrap();
|
||||
assert_eq!(
|
||||
fixture.store.recover_prepare(&intent).unwrap(),
|
||||
PrepareRecovery::ClearedUnmaterializedIntent
|
||||
);
|
||||
|
||||
let owned_id = PrepareOperationId::new([65; 32]);
|
||||
let owned_stage = Path::new("stages").join(hex(owned_id.as_bytes()));
|
||||
let owned_intent = PrepareOperation {
|
||||
operation_id: owned_id,
|
||||
stage_path: HostPath::new(owned_stage.as_os_str().as_encoded_bytes().to_vec()),
|
||||
..intent
|
||||
};
|
||||
fixture.catalog.record_prepare(&owned_intent).unwrap();
|
||||
std::fs::create_dir(fixture.root.join(&owned_stage)).unwrap();
|
||||
std::fs::write(fixture.root.join(&owned_stage).join("partial"), b"partial").unwrap();
|
||||
let metadata = std::fs::metadata(fixture.root.join(&owned_stage)).unwrap();
|
||||
let owned = PrepareOperation {
|
||||
stage_state: PrepareStageState::Owned,
|
||||
device: Some(DeviceId::new(metadata.dev())),
|
||||
inode: Some(InodeId::new(metadata.ino())),
|
||||
..owned_intent.clone()
|
||||
};
|
||||
fixture
|
||||
.catalog
|
||||
.bind_prepare_stage(&owned_intent, &owned)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fixture.store.recover_prepare(&owned).unwrap(),
|
||||
PrepareRecovery::RemovedOwnedStage
|
||||
);
|
||||
assert!(!fixture.root.join(&owned_stage).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reclaim_refuses_forged_identity_and_leases() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"same",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]);
|
||||
let artifact = fixture
|
||||
.store
|
||||
.prepare(&fixture.request(70, &[(bytes, OCI_TAR)]))
|
||||
.unwrap();
|
||||
let lease = lazaret_core::records::ArtifactLease {
|
||||
prepared_artifact_id: artifact.prepared_artifact_id,
|
||||
domain_id: lazaret_core::identity::DomainId::new([71; 32]),
|
||||
generation_id: lazaret_core::identity::GenerationId::new(1),
|
||||
};
|
||||
fixture.catalog.acquire_lease(&lease).unwrap();
|
||||
assert!(matches!(
|
||||
fixture.store.reclaim(&artifact),
|
||||
Err(error)
|
||||
if matches!(error.downcast_ref::<CatalogError>(), Some(CatalogError::LeasesPresent))
|
||||
));
|
||||
fixture.catalog.release_lease(&lease).unwrap();
|
||||
let mut forged = artifact.clone();
|
||||
forged.inode = InodeId::new(artifact.inode.get() + 1);
|
||||
assert!(fixture.store.reclaim(&forged).is_err());
|
||||
assert!(fixture.artifact_root(&artifact).exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_gc_is_idempotent_only_at_the_durable_reclaim_boundary() {
|
||||
let fixture = Fixture::new();
|
||||
let artifact = fixture
|
||||
.store
|
||||
.prepare(&fixture.request(
|
||||
71,
|
||||
&[(
|
||||
layer(&[Entry {
|
||||
path: "payload",
|
||||
kind: EntryType::Regular,
|
||||
data: b"gc",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}]),
|
||||
OCI_TAR,
|
||||
)],
|
||||
))
|
||||
.unwrap();
|
||||
let id = artifact.prepared_artifact_id;
|
||||
assert!(fixture.store.gc_artifacts(&[id, id]).is_err());
|
||||
|
||||
let lease = lazaret_core::records::ArtifactLease {
|
||||
prepared_artifact_id: id,
|
||||
domain_id: lazaret_core::identity::DomainId::new([72; 32]),
|
||||
generation_id: lazaret_core::identity::GenerationId::new(1),
|
||||
};
|
||||
fixture.catalog.acquire_lease(&lease).unwrap();
|
||||
assert!(fixture.store.gc_artifacts(&[id]).is_err());
|
||||
fixture.catalog.release_lease(&lease).unwrap();
|
||||
|
||||
let result = fixture.store.gc_artifacts(&[id]).unwrap();
|
||||
assert_eq!(result[0].prepared_artifact_id, id);
|
||||
assert_eq!(result[0].outcome, ReclaimOutcome::Removed);
|
||||
assert!(fixture.store.gc_artifacts(&[id]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destination_layout_is_materialized_and_canonical_in_artifact_identity() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer_with_owner(&[], 0, 0);
|
||||
let mut first = fixture.request(72, &[(bytes.clone(), OCI_TAR)]);
|
||||
first.id_map = id_map_with_container_root();
|
||||
first.destinations = vec![
|
||||
PreparedDestination {
|
||||
path: "/workspace".into(),
|
||||
kind: PreparedDestinationKind::Directory,
|
||||
},
|
||||
PreparedDestination {
|
||||
path: "/run/lazaret/bin/entry".into(),
|
||||
kind: PreparedDestinationKind::RegularFile,
|
||||
},
|
||||
PreparedDestination {
|
||||
path: "/workspace".into(),
|
||||
kind: PreparedDestinationKind::Directory,
|
||||
},
|
||||
];
|
||||
let artifact = fixture.store.prepare(&first).unwrap();
|
||||
let root = fixture.artifact_root(&artifact);
|
||||
assert!(root.join("workspace").is_dir());
|
||||
assert!(root.join("run/lazaret/bin/entry").is_file());
|
||||
|
||||
let mut reordered = fixture.request(73, &[(bytes.clone(), OCI_TAR)]);
|
||||
reordered.id_map = id_map_with_container_root();
|
||||
reordered.destinations = first.destinations.iter().rev().cloned().collect();
|
||||
assert_eq!(fixture.store.prepare(&reordered).unwrap(), artifact);
|
||||
|
||||
let mut unequal = fixture.request(74, &[(bytes, OCI_TAR)]);
|
||||
unequal.id_map = id_map_with_container_root();
|
||||
unequal.destinations = vec![PreparedDestination {
|
||||
path: "/opt/other".into(),
|
||||
kind: PreparedDestinationKind::Directory,
|
||||
}];
|
||||
assert_ne!(
|
||||
fixture
|
||||
.store
|
||||
.prepare(&unequal)
|
||||
.unwrap()
|
||||
.prepared_artifact_id,
|
||||
artifact.prepared_artifact_id
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destination_type_conflict_refuses_before_publication() {
|
||||
let fixture = Fixture::new();
|
||||
let bytes = layer_with_owner(
|
||||
&[Entry {
|
||||
path: "opt/lazaret-state",
|
||||
kind: EntryType::Regular,
|
||||
data: b"conflict",
|
||||
target: None,
|
||||
mode: 0o644,
|
||||
}],
|
||||
0,
|
||||
0,
|
||||
);
|
||||
let mut request = fixture.request(75, &[(bytes, OCI_TAR)]);
|
||||
request.id_map = id_map_with_container_root();
|
||||
request.destinations = vec![PreparedDestination {
|
||||
path: "/opt/lazaret-state".into(),
|
||||
kind: PreparedDestinationKind::Directory,
|
||||
}];
|
||||
assert!(fixture.store.prepare(&request).is_err());
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.prepared_artifacts
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
fn id_map_with_container_root() -> IdMap {
|
||||
IdMap {
|
||||
uid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::geteuid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
gid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::getegid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
fn hex(bytes: &[u8]) -> String {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
bytes.iter().fold(
|
||||
String::with_capacity(bytes.len() * 2),
|
||||
|mut output, byte| {
|
||||
write!(output, "{byte:02x}").expect("writing to String cannot fail");
|
||||
output
|
||||
},
|
||||
)
|
||||
}
|
||||
include!("prepared_lower/semantics.rs");
|
||||
include!("prepared_lower/publication.rs");
|
||||
|
|
|
|||
Loading…
Reference in a new issue