use super::*;
use crate::tests::support::encode_fixture;
use crate::tests::support::ic_snapshot_metadata::values;
use crate::{
model::ic_snapshot_metadata::{IcSnapshotMetadataReply, IcSnapshotMetadataRequest},
tests::support::ic_snapshot_upload::{
SOURCE_ID, TARGET, data_workflow, source_plan, unhex, upload_plan,
},
};
use ic_platform::management_canister_types::{ChunkHash, ReadCanisterSnapshotMetadataResult};
fn with_values(
values: ReadCanisterSnapshotMetadataResult,
check: impl FnOnce(&IcSnapshotUploadRequest<'_>),
) {
let source = source_plan();
let request = IcSnapshotMetadataRequest::new(TARGET, SOURCE_ID).unwrap();
let metadata =
IcSnapshotMetadataReply::decode(&request, &encode_fixture(values).unwrap()).unwrap();
let upload = IcSnapshotUploadRequest::metadata(
&source,
&metadata,
&ArtifactChecksumRecord::from_bytes(b"original tree"),
)
.unwrap();
check(&upload);
}
fn allocation<'a, 'b>(payload: &'a IcSnapshotUploadRequest<'b>) -> IcSnapshotUploadReply<'a, 'b> {
IcSnapshotUploadReply::decode(
payload,
&unhex("4449444c026c01b6b897890f016d7b0100031500ff"),
)
.unwrap()
}
#[test]
fn complete_exact_extents_keep_stage_headroom_and_allocation_reply_binding() {
let mut values = values("upload-source");
values.wasm_module_size = 5;
values.wasm_memory_size = 3;
values.stable_memory_size = 0;
values.wasm_chunk_store = vec![
ChunkHash { hash: vec![1; 32] },
ChunkHash { hash: vec![2; 32] },
];
with_values(values, |metadata| {
let workflow = data_workflow(metadata, 12);
let reply = allocation(metadata);
assert!(matches!(
DataUploadPreparation::new(&data_workflow(metadata, 4), 7, &reply, 4),
Err(SnapshotDataPlanningError::InsufficientAllowance {
required: 5,
original: 4
})
));
let preparation = DataUploadPreparation::new(&workflow, 7, &reply, 4).unwrap();
let kinds = preparation.kinds();
let expected = vec![
SnapshotDataKind::WasmModule { offset: 0, size: 4 },
SnapshotDataKind::WasmModule { offset: 4, size: 1 },
SnapshotDataKind::WasmMemory { offset: 0, size: 3 },
SnapshotDataKind::WasmChunk { hash: vec![1; 32] },
SnapshotDataKind::WasmChunk { hash: vec![2; 32] },
];
assert_eq!(
encode_fixture(kinds).unwrap(),
encode_fixture(expected).unwrap()
);
let bindings: Vec<_> = (0..kinds.len())
.map(|i| ArtifactChecksumRecord::from_bytes(&i.to_le_bytes()))
.collect();
let data = preparation.finish(bindings.clone()).unwrap();
assert_eq!(data.destination(), &[21, 0, 255]);
let plan = data.plan().unwrap();
assert_eq!(plan.budget().mutations(), 12);
assert_eq!(plan.budget().observations(), 1);
assert_eq!(plan.allocated_attempts().mutations, 5);
assert_eq!(plan.allocated_attempts().observations, 0);
for (i, binding) in bindings.iter().enumerate() {
let id = u64::try_from(i).unwrap();
assert_eq!(plan.operation(id).unwrap().request(), binding.hash());
assert_eq!(
plan.operation(id).unwrap().depends_on(),
id.checked_sub(1).into_iter().collect::<Vec<_>>()
);
}
let mut original = serde_json::to_value(upload_plan(metadata)).unwrap();
original["operations"][0]["operation_sequence"] = serde_json::json!(0);
original["operations"][0]["budget"]["observations"] = serde_json::json!(0);
original["budget"]["observations"] = serde_json::json!(0);
let original: OperationPlanRecord = serde_json::from_value(original).unwrap();
let binding = ExecutionStageBindingRecord::new(&workflow, 0, &original, vec![]).unwrap();
let predecessor = ExecutionStagePredecessorRecord::new(
0,
binding.digest(),
ArtifactChecksumRecord::from_bytes(b"qualified settlement"),
reply.digest(),
);
let bound = data.bind(&binding, &original, vec![predecessor]).unwrap();
data.validate_binding(&bound).unwrap();
assert!(data.bind(&binding, &original, vec![]).is_err());
let wrong = ExecutionStagePredecessorRecord::new(
0,
binding.digest(),
ArtifactChecksumRecord::from_bytes(b"qualified settlement"),
ArtifactChecksumRecord::from_bytes(b"other reply"),
);
assert!(matches!(
data.bind(&binding, &original, vec![wrong]),
Err(SnapshotDataPlanningError::OriginalMismatch)
));
assert!(
DataUploadPreparation::new(&workflow, 7, &reply, 4)
.unwrap()
.finish(bindings[..4].to_vec())
.is_err()
);
});
}
#[test]
fn unknown_stage_rejects_before_upload_planning() {
with_values(values("upload-source"), |metadata| {
assert!(matches!(
DataUploadPreparation::new(&data_workflow(metadata, 2), 99, &allocation(metadata), 4),
Err(SnapshotDataPlanningError::Plan(_))
));
});
}
#[test]
fn empty_regions_finish_without_a_placeholder_plan() {
let mut input = values("upload-source");
input.wasm_module_size = 0;
input.wasm_memory_size = 0;
input.stable_memory_size = 0;
input.wasm_chunk_store.clear();
with_values(input, |metadata| {
let reply = allocation(metadata);
let workflow = data_workflow(metadata, 1);
let plan = DataUploadPreparation::new(&workflow, 7, &reply, 4)
.unwrap()
.finish(vec![])
.unwrap();
assert!(plan.kinds().is_empty());
assert!(plan.plan().is_none());
});
}
#[test]
fn changed_original_context_and_data_acknowledgement_cannot_plan_allocation() {
let mut values = values("upload-source");
values.wasm_module_size = 8;
values.wasm_memory_size = 8;
values.stable_memory_size = 8;
with_values(values, |metadata| {
let workflow = data_workflow(metadata, 100);
let mut caller_fields = serde_json::to_value(&workflow).unwrap();
caller_fields["allocation"]["context"]["caller"] = serde_json::json!(TARGET);
let another_caller = serde_json::from_value(caller_fields).unwrap();
assert!(
DataUploadPreparation::new(&another_caller, 7, &allocation(metadata), 1024 * 1024)
.is_ok()
);
let mut fields = serde_json::to_value(&workflow).unwrap();
fields["allocation"]["context"]["release"] = serde_json::json!("99".repeat(32));
let changed = serde_json::from_value(fields).unwrap();
assert!(matches!(
DataUploadPreparation::new(&changed, 7, &allocation(metadata), 1024 * 1024),
Err(SnapshotDataPlanningError::OriginalMismatch)
));
let data = IcSnapshotUploadRequest::data(
metadata,
&[21, 0, 255],
SnapshotDataKind::WasmModule { offset: 0, size: 1 },
&[7],
)
.unwrap();
let reply = IcSnapshotUploadReply::decode(
&data,
crate::model::ic_lifecycle_reply::EMPTY_CANDID_REPLY,
)
.unwrap();
assert!(matches!(
DataUploadPreparation::new(&workflow, 7, &reply, 1024 * 1024),
Err(SnapshotDataPlanningError::OriginalMismatch)
));
});
}