Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions rs/registry/canister/canister/canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use registry_canister::{
DeployHostosToSomeNodes, UpdateNodesHostosVersionPayload,
},
do_update_ssh_readonly_access_for_all_unassigned_nodes::UpdateSshReadOnlyAccessForAllUnassignedNodesPayload,
do_update_standard_engine_replica_version::UpdateStandardEngineReplicaVersionPayload,
do_update_subnet::UpdateSubnetPayload,
do_update_subnet_admins::UpdateSubnetAdminsPayload,
do_update_unassigned_nodes_config::UpdateUnassignedNodesConfigPayload,
Expand Down Expand Up @@ -990,6 +991,18 @@ fn update_unassigned_nodes_config_(payload: UpdateUnassignedNodesConfigPayload)
recertify_registry();
}

#[unsafe(export_name = "canister_update update_standard_engine_replica_version")]
fn update_standard_engine_replica_version() {
check_caller_is_governance_and_log("update_standard_engine_replica_version");
over(candid_one, update_standard_engine_replica_version_);
}

#[candid_method(update, rename = "update_standard_engine_replica_version")]
fn update_standard_engine_replica_version_(payload: UpdateStandardEngineReplicaVersionPayload) {
registry_mut().do_update_standard_engine_replica_version(payload);
recertify_registry();
}

#[unsafe(export_name = "canister_update deploy_guestos_to_all_unassigned_nodes")]
fn deploy_guestos_to_all_unassigned_nodes() {
check_caller_is_governance_and_log("deploy_guestos_to_all_unassigned_nodes");
Expand Down
9 changes: 9 additions & 0 deletions rs/registry/canister/canister/registry.did
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,12 @@ type UpdateUnassignedNodesConfigPayload = record {
ssh_readonly_access : opt vec text;
};

type UpdateStandardEngineReplicaVersionPayload = record {
new_replica_version_id : text;
old_replica_version_id : text;
deployment_progress : float64;
};

type MigrateNodeOperatorPayload = record {
new_node_operator_id : opt principal;
old_node_operator_id : opt principal;
Expand Down Expand Up @@ -670,6 +676,9 @@ service : {
update_ssh_readonly_access_for_all_unassigned_nodes : (
UpdateSshReadOnlyAccessForAllUnassignedNodesPayload
) -> ();
update_standard_engine_replica_version : (
UpdateStandardEngineReplicaVersionPayload
) -> ();
update_subnet : (UpdateSubnetPayload) -> ();
update_subnet_admins : (UpdateSubnetAdminsPayload) -> ();
update_unassigned_nodes_config : (UpdateUnassignedNodesConfigPayload) -> ();
Expand Down
9 changes: 9 additions & 0 deletions rs/registry/canister/canister/registry_test.did
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,12 @@ type UpdateUnassignedNodesConfigPayload = record {
ssh_readonly_access : opt vec text;
};

type UpdateStandardEngineReplicaVersionPayload = record {
new_replica_version_id : text;
old_replica_version_id : text;
deployment_progress : float64;
};

type MigrateNodeOperatorPayload = record {
new_node_operator_id : opt principal;
old_node_operator_id : opt principal;
Expand Down Expand Up @@ -670,6 +676,9 @@ service : {
update_ssh_readonly_access_for_all_unassigned_nodes : (
UpdateSshReadOnlyAccessForAllUnassignedNodesPayload
) -> ();
update_standard_engine_replica_version : (
UpdateStandardEngineReplicaVersionPayload
) -> ();
update_subnet : (UpdateSubnetPayload) -> ();
update_subnet_admins : (UpdateSubnetAdminsPayload) -> ();
update_unassigned_nodes_config : (UpdateUnassignedNodesConfigPayload) -> ();
Expand Down
4 changes: 4 additions & 0 deletions rs/registry/canister/src/invariants/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
node_record::check_node_record_invariants,
replica_version::check_replica_version_invariants,
routing_table::{check_canister_migrations_invariants, check_routing_table_invariants},
standard_engine_replica_version::check_standard_engine_replica_version_invariants,
subnet::check_subnet_invariants,
unassigned_nodes_config::check_unassigned_nodes_config_invariants,
},
Expand Down Expand Up @@ -117,6 +118,9 @@ impl Registry {
// NodeRecord invariants.
result = result.and(check_node_record_invariants(&snapshot));

// Standard engine replica version invariants
result = result.and(check_standard_engine_replica_version_invariants(&snapshot));

if let Err(e) = result {
panic!(
"{}invariant check failed with message: {}",
Expand Down
1 change: 1 addition & 0 deletions rs/registry/canister/src/invariants/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ mod node_operator;
mod node_record;
mod replica_version;
mod routing_table;
mod standard_engine_replica_version;
pub(crate) mod subnet;
mod unassigned_nodes_config;
155 changes: 145 additions & 10 deletions rs/registry/canister/src/invariants/replica_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ use crate::invariants::common::{

use ic_base_types::SubnetId;
use ic_protobuf::registry::{
replica_version::v1::ReplicaVersionRecord, subnet::v1::SubnetRecord,
unassigned_nodes_config::v1::UnassignedNodesConfigRecord,
replica_version::v1::ReplicaVersionRecord,
standard_engine_replica_version::v1::StandardEngineReplicaVersionRecord,
subnet::v1::SubnetRecord, unassigned_nodes_config::v1::UnassignedNodesConfigRecord,
};
use ic_registry_keys::{
make_replica_version_key, make_subnet_record_key, make_unassigned_nodes_config_record_key,
make_replica_version_key, make_standard_engine_replica_version_record_key,
make_subnet_record_key, make_unassigned_nodes_config_record_key,
};
use prost::Message;

Expand Down Expand Up @@ -40,6 +42,7 @@ pub(crate) fn check_replica_version_invariants(
if let Some(version) = unassigned_version_id {
versions_in_use.insert(version);
}
versions_in_use.append(&mut get_all_standard_engine_replica_versions(snapshot));
versions_in_use.append(&mut get_all_api_boundary_node_versions(snapshot));

let elected_set: BTreeSet<_> = get_all_replica_version_records(snapshot)
Expand Down Expand Up @@ -104,6 +107,21 @@ fn get_all_api_boundary_node_versions(snapshot: &RegistrySnapshot) -> BTreeSet<S
.collect()
}

/// Returns the replica versions referenced by the
/// StandardEngineReplicaVersionRecord (i.e. new_replica_version_id and
/// old_replica_version_id).
fn get_all_standard_engine_replica_versions(snapshot: &RegistrySnapshot) -> BTreeSet<String> {
snapshot
.get(make_standard_engine_replica_version_record_key().as_bytes())
.map(|bytes| {
let record = StandardEngineReplicaVersionRecord::decode(bytes.as_slice()).unwrap();
[record.new_replica_version_id, record.old_replica_version_id]
})
.into_iter()
.flatten()
.collect()
}

#[cfg(test)]
mod tests {
use crate::common::test_helpers::invariant_compliant_registry;
Expand All @@ -120,19 +138,18 @@ mod tests {
const MOCK_HASH: &str = "C0FFEEC0FFEEC0FFEEC0FFEEC0FFEEC0FFEEC0FFEEC0FFEEC0FFEEC0FFEED00D";
const MOCK_URL: &str = "http://release_package.tar.gz";

// Replica version IDs are git commit IDs (pointing to the source code used
// to build the Replica).
const REPLICA_VERSION_ID_1: &str = "eb3ab997954f2a91db8a42f84132cf37078d481c";
const REPLICA_VERSION_ID_2: &str = "63d086714a1e2bc6b0615008d5582f527d554cd3";

fn elect_version_mutations(versions: Vec<String>) -> Vec<RegistryMutation> {
versions
.into_iter()
.map(|v| {
let value = ReplicaVersionRecord {
release_package_sha256_hex: "".to_string(),
release_package_urls: Vec::new(),
guest_launch_measurements: None,
};

insert(
make_replica_version_key(v).as_bytes(),
value.encode_to_vec(),
ReplicaVersionRecord::default().encode_to_vec(),
)
})
.collect()
Expand Down Expand Up @@ -191,6 +208,91 @@ mod tests {
registry.check_global_state_invariants(&mutation);
}

#[test]
fn test_can_update_standard_engine_replica_version_record() {
// Step 1: Prepare the world. In particular, elect a couple of replica versions.
let mut registry = invariant_compliant_registry(0);
registry.maybe_apply_mutation_internal(elect_version_mutations(vec![
REPLICA_VERSION_ID_1.to_string(),
REPLICA_VERSION_ID_2.to_string(),
]));

// Step 2: Run the code under test.

// Prepare the mutation that's supposed to succeed.
let key = make_standard_engine_replica_version_record_key();
let value = StandardEngineReplicaVersionRecord {
new_replica_version_id: REPLICA_VERSION_ID_1.to_string(),
old_replica_version_id: REPLICA_VERSION_ID_2.to_string(),
deployment_progress: 0.1,
}
.encode_to_vec();

// Attempt the mutation.
let mutation = vec![insert(key.as_bytes(), value)];
registry.check_global_state_invariants(&mutation);

// Step 3: Verify result(s).
// The implicit assertion here is the previous line did not panic.
}

#[test]
#[should_panic(expected = "Using a version that isn't elected.")]
fn panic_when_new_replica_version_is_not_elected_in_standard_engine_replica_version_record() {
// Step 1: Prepare the world. Elect old, but not new.
let mut registry = invariant_compliant_registry(0);
registry.maybe_apply_mutation_internal(elect_version_mutations(vec![
REPLICA_VERSION_ID_1.to_string(),
]));

// Step 2: Run the code under test.

// Prepare mutation.
let key = make_standard_engine_replica_version_record_key();
let value = StandardEngineReplicaVersionRecord {
new_replica_version_id: "garbage".to_string(), // <- This is the bomb.
old_replica_version_id: REPLICA_VERSION_ID_1.to_string(),
deployment_progress: 0.1,
}
.encode_to_vec();

// Attempt mutation.
let mutation = vec![insert(key.as_bytes(), value)];
registry.check_global_state_invariants(&mutation);

// Step 3: Verify result(s).
// The assertion is at the top: #[should_panic(...)].
}

#[test]
#[should_panic(expected = "Using a version that isn't elected.")]
fn panic_when_old_replica_version_is_not_elected_in_standard_engine_replica_version_record() {
// Step 1: Prepare the world. Elect new, but not old.
// (Same initial state as previous test.)
let mut registry = invariant_compliant_registry(0);
registry.maybe_apply_mutation_internal(elect_version_mutations(vec![
REPLICA_VERSION_ID_1.to_string(),
]));

// Step 2: Run the code under test.

// Prepare mutation.
let key = make_standard_engine_replica_version_record_key();
let value = StandardEngineReplicaVersionRecord {
new_replica_version_id: REPLICA_VERSION_ID_1.to_string(),
old_replica_version_id: "garbage".to_string(), // <- This is the bomb.
deployment_progress: 0.1,
}
.encode_to_vec();

// Attempt mutation.
let mutation = vec![insert(key.as_bytes(), value)];
registry.check_global_state_invariants(&mutation);

// Step 3: Verify result(s).
// The assertion is at the top: #[should_panic(...)].
}

#[test]
#[should_panic(expected = "Using a version that isn't elected.")]
fn panic_when_retiring_a_version_in_use() {
Expand Down Expand Up @@ -236,6 +338,39 @@ mod tests {
registry.check_global_state_invariants(&mutation);
}

#[test]
#[should_panic(expected = "Using a version that isn't elected.")]
fn panic_when_retiring_a_version_referenced_by_standard_engine_record() {
// Step 1: Prepare the world.

// Step 1.1: Elect two replica versions.
let mut registry = invariant_compliant_registry(0);
registry.maybe_apply_mutation_internal(elect_version_mutations(vec![
REPLICA_VERSION_ID_1.to_string(),
REPLICA_VERSION_ID_2.to_string(),
]));

// Step 1.2: Start upgrading engines from one elected version to the
// other.
registry.maybe_apply_mutation_internal(vec![insert(
make_standard_engine_replica_version_record_key(),
StandardEngineReplicaVersionRecord {
new_replica_version_id: REPLICA_VERSION_ID_2.to_string(),
old_replica_version_id: REPLICA_VERSION_ID_1.to_string(),
deployment_progress: 0.1,
}
.encode_to_vec(),
)]);

// Step 2: Run the code under test. Try to un-elect one of the versions
// referenced by the (engine replica version) upgrade.
let mutation = delete(make_replica_version_key(REPLICA_VERSION_ID_2).as_bytes());
registry.check_global_state_invariants(&[mutation]);

// Step 3: Verify result(s).
// The assertion is at the top: #[should_panic(...)].
}

fn check_replica_version(hash: &str, urls: Vec<String>) {
let registry = invariant_compliant_registry(0);

Expand Down
Loading
Loading