From c1abad5ef5924f609a16918fad499e86ba767195 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 31 Jul 2024 05:27:02 -0500 Subject: [PATCH 01/22] outline v_replica_set_controller liveness proof Signed-off-by: Cody Rivera --- .../proof/helper_invariants/mod.rs | 13 + .../proof/helper_invariants/owner_ref.rs | 29 + .../proof/helper_invariants/predicate.rs | 27 + .../proof/helper_invariants/proof.rs | 24 + .../proof/helper_invariants/unchangeable.rs | 31 + .../proof/helper_invariants/validation.rs | 30 + .../proof/liveness/mod.rs | 7 + .../proof/liveness/proof.rs | 289 +++++ .../proof/liveness/resource_match.rs | 989 ++++++++++++++++++ .../proof/liveness/spec.rs | 574 ++++++++++ .../proof/liveness/stateful_set_match.rs | 531 ++++++++++ .../proof/liveness/terminate.rs | 250 +++++ .../v_replica_set_controller/proof/mod.rs | 5 + .../proof/predicate.rs | 23 + 14 files changed, 2822 insertions(+) create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/proof.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs create mode 100644 src/controller_examples/v_replica_set_controller/proof/predicate.rs diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs new file mode 100644 index 000000000..f9f2a23a9 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs @@ -0,0 +1,13 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +pub mod owner_ref; +pub mod predicate; +pub mod proof; +pub mod unchangeable; +pub mod validation; + +pub use owner_ref::*; +pub use predicate::*; +pub use proof::*; +pub use unchangeable::*; +pub use validation::*; diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs new file mode 100644 index 000000000..0fb5cd2b8 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs @@ -0,0 +1,29 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, + stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::v_replica_set_controller::{ + model::reconciler::*, + proof::{ + helper_invariants::{predicate::*, proof::*}, + predicate::*, + }, + trusted::{spec_types::*, step::*}, +}; +use vstd::prelude::*; + +verus! { + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs new file mode 100644 index 000000000..2cc3febb2 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -0,0 +1,27 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, + stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::reconciler::spec::reconciler::*; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{multiset_lib, seq_lib, string_view::*}; +use crate::v_replica_set_controller::{ + model::reconciler::*, + proof::{predicate::*}, + trusted::{spec_types::*, step::*}, +}; +use vstd::{multiset::*, prelude::*, string::*}; + +verus! { + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/proof.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/proof.rs new file mode 100644 index 000000000..8422475ab --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/proof.rs @@ -0,0 +1,24 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use super::predicate::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{multiset_lib, seq_lib, string_view::*}; +use crate::v_replica_set_controller::{ + proof::{predicate::*}, + trusted::{liveness_theorem::*, spec_types::*, step::*}, +}; +use vstd::{multiset::*, prelude::*, string::*}; + +verus! { + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs new file mode 100644 index 000000000..5e2c8c356 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs @@ -0,0 +1,31 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::reconciler::spec::{reconciler::*, resource_builder::*}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{multiset_lib, seq_lib::*, string_view::*}; +use crate::v_replica_set_controller::{ + model::{reconciler::*}, + proof::{ + helper_invariants::{owner_ref::*, predicate::*, proof::*, validation::*}, + predicate::*, + }, + trusted::{ + spec_types::*, step::*, + }, +}; +use vstd::{multiset::*, prelude::*, seq_lib::*, string::*}; + +verus! { + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs new file mode 100644 index 000000000..624d93cc8 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs @@ -0,0 +1,30 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, + stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::reconciler::spec::{reconciler::*, resource_builder::*}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{multiset_lib, seq_lib, string_view::*}; +use crate::v_replica_set_controller::{ + model::{reconciler::*}, + proof::{ + helper_invariants::{owner_ref::*, predicate::*, proof::*}, + predicate::*, + }, + trusted::{spec_types::*, step::*}, +}; +use vstd::{multiset::*, prelude::*, string::*}; + +verus! { + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs new file mode 100644 index 000000000..9013484db --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +pub mod proof; +// pub mod resource_match; +pub mod spec; +// pub mod stateful_set_match; +// pub mod terminate; diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs new file mode 100644 index 000000000..267318427 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs @@ -0,0 +1,289 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{map_lib::*, string_view::*}; +use crate::v_replica_set_controller::{ + model::{reconciler::*}, + proof::{ + //helper_invariants, + liveness::{ + //resource_match::*, + spec::*, + // stateful_set_match::{ + // lemma_from_after_get_stateful_set_step_to_stateful_set_matches, + // lemma_stateful_set_is_stable, + // }, + // terminate, + //zookeeper_api::lemma_from_after_exists_stateful_set_step_to_after_get_stateful_set_step, + }, + // predicate::*, + // resource::*, + }, + trusted::{liveness_theorem::*, spec_types::*, step::*}, +}; +use vstd::{prelude::*, string::*}; + +verus! { + +// We prove init /\ []next /\ []wf |= []VReplicaSet::desired_state_is(vrs) ~> []current_state_matches(vrs) holds for each vrs. +proof fn liveness_proof_forall_vrs() + ensures liveness_theorem(), +{ + assert forall |vrs: VReplicaSetView| #[trigger] cluster_spec().entails(liveness(vrs)) by { + liveness_proof(vrs); + }; + spec_entails_tla_forall(cluster_spec(), |vrs: VReplicaSetView| liveness(vrs)); +} + +proof fn liveness_proof(vrs: VReplicaSetView) + ensures cluster_spec().entails(liveness(vrs)), +{ + assumption_and_invariants_of_all_phases_is_stable(vrs); + lemma_true_leads_to_always_current_state_matches(vrs); + reveal_with_fuel(spec_before_phase_n, 8); + spec_before_phase_n_entails_true_leads_to_current_state_matches(7, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(6, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(5, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(4, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(3, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(2, vrs); + spec_before_phase_n_entails_true_leads_to_current_state_matches(1, vrs); + + let assumption = always(lift_state(VRSCluster::desired_state_is(vrs))); + unpack_conditions_from_spec(invariants(vrs), assumption, true_pred(), always(lift_state(current_state_matches(vrs)))); + temp_pred_equality(true_pred().and(assumption), assumption); + + valid_implies_trans( + cluster_spec().and(derived_invariants_since_beginning(vrs)), invariants(vrs), + always(lift_state(VRSCluster::desired_state_is(vrs))).leads_to(always(lift_state(current_state_matches(vrs)))) + ); + sm_spec_entails_all_invariants(vrs); + simplify_predicate(cluster_spec(), derived_invariants_since_beginning(vrs)); +} + +proof fn spec_before_phase_n_entails_true_leads_to_current_state_matches(i: nat, vrs: VReplicaSetView) + requires + 1 <= i <= 7, + valid(stable(spec_before_phase_n(i, vrs))), + spec_before_phase_n(i + 1, vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))) + ensures spec_before_phase_n(i, vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))), +{ + assume(false); + // reveal_with_fuel(spec_before_phase_n, 8); + // temp_pred_equality(spec_before_phase_n(i + 1, zookeeper), spec_before_phase_n(i, zookeeper).and(invariants_since_phase_n(i, zookeeper))); + // spec_of_previous_phases_entails_eventually_new_invariants(i, zookeeper); + // unpack_conditions_from_spec(spec_before_phase_n(i, zookeeper), invariants_since_phase_n(i, zookeeper), true_pred(), always(lift_state(current_state_matches::(zookeeper)))); + // temp_pred_equality(true_pred().and(invariants_since_phase_n(i, zookeeper)), invariants_since_phase_n(i, zookeeper)); + // leads_to_trans_temp(spec_before_phase_n(i, zookeeper), true_pred(), invariants_since_phase_n(i, zookeeper), always(lift_state(current_state_matches::(zookeeper)))); +} + +proof fn lemma_true_leads_to_always_current_state_matches(vrs: VReplicaSetView) + ensures assumption_and_invariants_of_all_phases(vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))), +{ + assume(false); + // let spec = assumption_and_invariants_of_all_phases(vrs); + + // assert forall |action: ActionKind, sub_resource: SubResource| #![auto] spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource)))))) by { + // always_tla_forall_apply(spec, |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))), (action, sub_resource)); + // } + + // // The use of termination property ensures spec |= true ~> reconcile_idle. + // terminate::reconcile_eventually_terminates(spec, zookeeper); + // // Then we can continue to show that spec |= reconcile_idle ~> []current_state_matches(zookeeper). + + // // The following two lemmas show that spec |= reconcile_idle ~> init /\ no_pending_req. + // lemma_from_reconcile_idle_to_scheduled(spec, zookeeper); + // lemma_from_scheduled_to_init_step(spec, zookeeper); + + // // After applying this lemma, we get spec |= init /\ no_pending_req ~> create_headless_service /\ pending_req. + // lemma_from_init_step_to_after_create_headless_service_step(spec, zookeeper); + + // // always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper)), SubResource::ConfigMap); + + // // We first show that the reconciler can go to at_after_get_resource_step(next_resource) from at_after_get_resource_step(sub_resource) + // // where sub_resource cannot be StatefulSet because it's the last resource to be processed and doesn't have its next_resource. + // // Through this, we can string all the resources together in sequence. This also means that the system can go to any + // // at_after_get_resource_step(sub_resource) from an arbitrary state. + // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet && sub_resource != SubResource::ConfigMap implies + // spec.entails( + // lift_state(#[trigger] pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) + // .leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) + // ) by { + // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); + // lemma_from_after_get_resource_step_to_resource_matches(spec, zookeeper, sub_resource); + // } + // // Thanks to the recursive construction of macro. + // leads_to_trans_n!( + // spec, true_pred(), lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }), + // lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) && s.scheduled_reconciles().contains_key(zookeeper.object_ref())}), + // lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)), + // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper)), + // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::ClientService, zookeeper)), + // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::AdminServerService, zookeeper)), + // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::ConfigMap, zookeeper)) + // ); + + // // Since we already have true ~> at_after_get_resource_step(sub_resource), and we can get at_after_get_resource_step(sub_resource) + // // ~> sub_resource_state_matches(sub_resource, zookeeper) by applying lemma lemma_from_after_get_resource_step_to_resource_matches, + // // we now have true ~> sub_resource_state_matches(sub_resource, zookeeper). + // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet implies + // spec.entails( + // true_pred().leads_to(lift_state(#[trigger] sub_resource_state_matches(sub_resource, zookeeper))) + // ) by { + // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); + // lemma_from_after_get_resource_step_to_resource_matches(spec, zookeeper, sub_resource); + // leads_to_trans_temp( + // spec, true_pred(), lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)), + // lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + // ); + // } + + // // Now we further prove stability: given true ~> sub_resource_state_matches(sub_resource, zookeeper) + // // we prove true ~> []sub_resource_state_matches(sub_resource, zookeeper) + // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet implies + // spec.entails( + // true_pred().leads_to(always(lift_state(#[trigger] sub_resource_state_matches(sub_resource, zookeeper)))) + // ) by { + // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); + // lemma_resource_object_is_stable(spec, sub_resource, zookeeper, true_pred()); + // } +} + +// proof fn lemma_from_reconcile_idle_to_scheduled(spec: TempPred, vrs: VReplicaSetView) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::schedule_controller_reconcile().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), +// ensures +// spec.entails(lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) +// .leads_to(lift_state(|s: ZKCluster| { +// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) +// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) +// }))), +// { +// let pre = |s: ZKCluster| { +// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) +// &&& !s.scheduled_reconciles().contains_key(zookeeper.object_ref()) +// }; +// let post = |s: ZKCluster| { +// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) +// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) +// }; +// let input = zookeeper.object_ref(); +// ZKCluster::lemma_pre_leads_to_post_by_schedule_controller_reconcile_borrow_from_spec(spec, input, ZKCluster::next(), ZKCluster::desired_state_is(zookeeper), pre, post); +// valid_implies_implies_leads_to(spec, lift_state(post), lift_state(post)); +// or_leads_to_combine_temp(spec, lift_state(pre), lift_state(post), lift_state(post)); +// temp_pred_equality(lift_state(pre).or(lift_state(post)), lift_state(|s: ZKCluster| {!s.ongoing_reconciles().contains_key(zookeeper.object_ref())})); +// } + +// proof fn lemma_from_scheduled_to_init_step(spec: TempPred, vrs: VReplicaSetView) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))), +// spec.entails(always(lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)))), +// ensures +// spec.entails(lift_state(|s: ZKCluster| { +// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) +// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) +// }).leads_to(lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)))), +// { +// let pre = |s: ZKCluster| { +// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) +// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) +// }; +// let post = no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init); +// let input = (None, Some(zookeeper.object_ref())); +// let stronger_next = |s, s_prime| { +// &&& ZKCluster::next()(s, s_prime) +// &&& ZKCluster::crash_disabled()(s) +// &&& ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()(s) +// &&& ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)(s) +// }; +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), +// lift_action(ZKCluster::next()), +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), +// lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)) +// ); +// ZKCluster::lemma_pre_leads_to_post_by_controller(spec, input, stronger_next, ZKCluster::run_scheduled_reconcile(), pre, post); +// } + +// proof fn lemma_from_init_step_to_after_create_headless_service_step(spec: TempPred, vrs: VReplicaSetView) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// ensures +// spec.entails(lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper)))), +// { +// let pre = no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init); +// let post = pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper); +// let input = (None, Some(zookeeper.object_ref())); +// let stronger_next = |s, s_prime: ZKCluster| { +// &&& ZKCluster::next()(s, s_prime) +// &&& ZKCluster::crash_disabled()(s) +// }; +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), lift_action(ZKCluster::next()), lift_state(ZKCluster::crash_disabled()) +// ); +// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { +// let step = choose |step| ZKCluster::next_step(s, s_prime, step); +// match step { +// Step::ControllerStep(input) => { +// if input.1.get_Some_0() != zookeeper.object_ref() { +// assert(pre(s_prime)); +// } else { +// assert(post(s_prime)); +// } +// }, +// _ => { +// assert(pre(s_prime)); +// } +// } +// } +// ZKCluster::lemma_pre_leads_to_post_by_controller(spec, input, stronger_next, ZKCluster::continue_reconcile(), pre, post); +// } + +// proof fn always_tla_forall_apply_for_sub_resource(spec: TempPred, sub_resource: SubResource, vrs: VReplicaSetView) +// requires +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(res, zookeeper))))), +// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(res, zookeeper))))), +// ensures +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), +// { +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(res, zookeeper)), sub_resource); +// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(res, zookeeper)), sub_resource); +// } + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs new file mode 100644 index 000000000..fd9ae2625 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -0,0 +1,989 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{map_lib::*, string_view::*}; +use crate::zookeeper_controller::{ + model::{reconciler::*, resource::*}, + proof::{helper_invariants, predicate::*, resource::*}, + trusted::{spec_types::*, step::*}, +}; +use vstd::{prelude::*, string::*}; + +verus! { + +pub proof fn lemma_from_after_get_resource_step_to_resource_matches( + spec: TempPred, zookeeper: ZookeeperClusterView, sub_resource: SubResource +) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), + ensures + spec.entails( + lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) + .leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) + ), + sub_resource != SubResource::ConfigMap ==> spec.entails( + lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) + .leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) + ), + sub_resource == SubResource::ConfigMap ==> spec.entails( + lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) + .leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + ), +{ + lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches(spec, sub_resource, zookeeper); + lemma_from_after_get_resource_step_and_key_exists_to_resource_matches(spec, sub_resource, zookeeper); + let key_not_exists = lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }); + let key_exists = lift_state(|s: ZKCluster| { + &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }); + or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); + temp_pred_equality( + key_not_exists.or(key_exists), lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) + ); + + let next_state = if sub_resource != SubResource::ConfigMap { + pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) + } else { + pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type + }; + or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(next_state)); +} + +pub proof fn lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches( + spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView +) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), + ensures + spec.entails( + lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) + ), + sub_resource != SubResource::ConfigMap && sub_resource != SubResource::StatefulSet ==> spec.entails( + lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) + ), + sub_resource == SubResource::ConfigMap ==> spec.entails( + lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + ), +{ + let pre = lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }); + let post = lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) + }); + let pre_and_req_in_flight = |req_msg| lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) + }); + let pre_and_exists_resp_in_flight = lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) + }); + let pre_and_resp_in_flight = |resp_msg| lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }); + let post_and_req_in_flight = |req_msg| lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) + }); + let match_and_ok_resp = lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); + let next_state = if sub_resource != SubResource::ConfigMap { + pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) + } else { + pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type + }; + + assert_by(spec.entails(pre.leads_to(match_and_ok_resp)), { + assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(pre_and_exists_resp_in_flight)) by { + lemma_from_key_not_exists_to_receives_not_found_resp_at_after_get_resource_step(spec, sub_resource, zookeeper, req_msg); + } + leads_to_exists_intro(spec, pre_and_req_in_flight, pre_and_exists_resp_in_flight); + assert_by(tla_exists(pre_and_req_in_flight) == pre, { + assert forall |ex| #[trigger] pre.satisfied_by(ex) implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(pre_and_req_in_flight), pre); + }); + + assert forall |resp_msg| spec.entails(#[trigger] pre_and_resp_in_flight(resp_msg).leads_to(post)) by { + lemma_from_after_get_resource_step_to_after_create_resource_step(spec, sub_resource, zookeeper, resp_msg); + } + leads_to_exists_intro(spec, pre_and_resp_in_flight, post); + assert_by(tla_exists(pre_and_resp_in_flight) == pre_and_exists_resp_in_flight, { + assert forall |ex| #[trigger] pre_and_exists_resp_in_flight.satisfied_by(ex) implies tla_exists(pre_and_resp_in_flight).satisfied_by(ex) by { + let resp_msg = choose |resp_msg| { + &&& #[trigger] ex.head().in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }; + assert(pre_and_resp_in_flight(resp_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(pre_and_resp_in_flight), pre_and_exists_resp_in_flight); + }); + + assert forall |req_msg| spec.entails(#[trigger] post_and_req_in_flight(req_msg).leads_to(match_and_ok_resp)) by { + lemma_resource_state_matches_at_after_create_resource_step(spec, sub_resource, zookeeper, req_msg); + } + leads_to_exists_intro(spec, post_and_req_in_flight, match_and_ok_resp); + assert_by(tla_exists(post_and_req_in_flight) == post, { + assert forall |ex| #[trigger] post.satisfied_by(ex) implies tla_exists(post_and_req_in_flight).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + assert(post_and_req_in_flight(req_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(post_and_req_in_flight), post); + }); + leads_to_trans_n!(spec, pre, pre_and_exists_resp_in_flight, post, match_and_ok_resp); + }); + + assert_by(spec.entails(pre.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), { + valid_implies_implies_leads_to(spec, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); + leads_to_trans_n!(spec, pre, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); + }); + + // We already have the desired state. + // Now prove the system can successfully enter the next state. + if sub_resource != SubResource::StatefulSet { + assert_by(spec.entails(pre.leads_to(lift_state(next_state))), { + let known_ok_resp = |resp_msg: ZKMessage| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_create_resource_step(sub_resource, zookeeper, resp_msg)); + assert forall |resp_msg| spec.entails(#[trigger] known_ok_resp(resp_msg).leads_to(lift_state(next_state))) by { + let pre = resp_msg_is_the_in_flight_ok_resp_at_after_create_resource_step(sub_resource, zookeeper, resp_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) + }; + + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || next_state(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ControllerStep(input) => { + if input.1.is_Some() && input.1.get_Some_0() == zookeeper.object_ref() { + assert(next_state(s_prime)); + } else { + assert(pre(s_prime)); + } + } + _ => { + assert(pre(s_prime)); + } + } + } + ZKCluster::lemma_pre_leads_to_post_by_controller( + spec, (Some(resp_msg), Some(zookeeper.object_ref())), stronger_next, ZKCluster::continue_reconcile(), pre, next_state + ); + } + leads_to_exists_intro(spec, known_ok_resp, lift_state(next_state)); + let exists_ok_resp = lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)); + assert_by(tla_exists(known_ok_resp) == exists_ok_resp, { + assert forall |ex| #[trigger] exists_ok_resp.satisfied_by(ex) implies tla_exists(known_ok_resp).satisfied_by(ex) by { + let resp_msg = choose |resp_msg| { + &&& #[trigger] ex.head().in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) + &&& resp_msg.content.get_create_response().res.is_Ok() + &&& state_after_create(sub_resource, zookeeper, resp_msg.content.get_create_response().res.get_Ok_0(), ex.head().ongoing_reconciles()[zookeeper.object_ref()].local_state).is_Ok() + }; + assert(known_ok_resp(resp_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(known_ok_resp), exists_ok_resp); + }); + valid_implies_implies_leads_to(spec, match_and_ok_resp, exists_ok_resp); + leads_to_trans_n!(spec, pre, match_and_ok_resp, exists_ok_resp, lift_state(next_state)); + }); + } +} + +proof fn lemma_from_after_get_resource_step_and_key_exists_to_resource_matches( + spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView +) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), + ensures + spec.entails( + lift_state(|s: ZKCluster| { + &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) + ), + sub_resource != SubResource::ConfigMap ==> spec.entails( + lift_state(|s: ZKCluster| { + &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) + ), + sub_resource == SubResource::ConfigMap ==> spec.entails( + lift_state(|s: ZKCluster| { + &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }).leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + ), +{ + let resource_key = get_request(sub_resource, zookeeper).key; + let pre = lift_state(|s: ZKCluster| { + &&& s.resources().contains_key(resource_key) + &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) + }); + let post = pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper); + let match_and_ok_resp = lift_state(sub_resource_state_matches(sub_resource, zookeeper)).and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); + let next_state = if sub_resource != SubResource::ConfigMap { + pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) + } else { + pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type + }; + assert_by(spec.entails(pre.leads_to(match_and_ok_resp)), { + let pre_and_req_in_flight = |req_msg| lift_state(req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg)); + assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)))) + by { + lemma_from_key_exists_to_receives_ok_resp_at_after_get_resource_step(spec, sub_resource, zookeeper, req_msg); + } + leads_to_exists_intro(spec, pre_and_req_in_flight, lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); + assert_by(tla_exists(pre_and_req_in_flight) == pre, { + assert forall |ex| #[trigger] pre.satisfied_by(ex) + implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(pre_and_req_in_flight), pre); + }); + let pre_and_resp_in_flight = |resp_msg| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)); + assert forall |resp_msg| spec.entails(#[trigger] pre_and_resp_in_flight(resp_msg).leads_to(lift_state(post))) + by { + lemma_from_after_get_resource_step_to_after_update_resource_step(spec, sub_resource, zookeeper, resp_msg); + } + leads_to_exists_intro(spec, pre_and_resp_in_flight, lift_state(post)); + assert_by(tla_exists(pre_and_resp_in_flight) == lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)), { + assert forall |ex| #[trigger] lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)).satisfied_by(ex) + implies tla_exists(pre_and_resp_in_flight).satisfied_by(ex) by { + let resp_msg = choose |resp_msg| { + &&& #[trigger] ex.head().in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) + &&& resp_msg.content.get_get_response().res.is_Ok() + &&& resp_msg.content.get_get_response().res.get_Ok_0() == ex.head().resources()[resource_key] + }; + assert(pre_and_resp_in_flight(resp_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(pre_and_resp_in_flight), lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); + }); + let pre_and_req_in_flight = |req_msg| lift_state(req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg)); + assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(match_and_ok_resp)) by { + lemma_resource_state_matches_at_after_update_resource_step(spec, sub_resource, zookeeper, req_msg); + } + leads_to_exists_intro(spec, pre_and_req_in_flight, match_and_ok_resp); + assert_by(tla_exists(pre_and_req_in_flight) == lift_state(post), { + assert forall |ex| #[trigger] lift_state(post).satisfied_by(ex) + implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(pre_and_req_in_flight), lift_state(post)); + }); + leads_to_trans_n!( + spec, pre, lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)), + lift_state(post), match_and_ok_resp + ); + }); + + assert_by(spec.entails(pre.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), { + valid_implies_implies_leads_to(spec, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); + leads_to_trans_n!(spec, pre, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); + }); + + // We already have the desired state. + // Now prove the system can successfully enter the next state. + assert_by(spec.entails(pre.leads_to(lift_state(next_state))), { + let known_ok_resp = |resp_msg: ZKMessage| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_update_resource_step(sub_resource, zookeeper, resp_msg)); + assert forall |resp_msg| spec.entails(#[trigger] known_ok_resp(resp_msg).leads_to(lift_state(next_state))) by { + let pre = resp_msg_is_the_in_flight_ok_resp_at_after_update_resource_step(sub_resource, zookeeper, resp_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())) + ); + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || next_state(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ControllerStep(input) => { + if input.1.is_Some() && input.1.get_Some_0() == zookeeper.object_ref() { + assert(next_state(s_prime)); + } else { + assert(pre(s_prime)); + } + } + _ => { + assert(pre(s_prime)); + } + } + } + ZKCluster::lemma_pre_leads_to_post_by_controller( + spec, (Some(resp_msg), Some(zookeeper.object_ref())), stronger_next, ZKCluster::continue_reconcile(), pre, next_state + ); + } + leads_to_exists_intro(spec, known_ok_resp, lift_state(next_state)); + let exists_ok_resp = lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)); + assert_by(tla_exists(known_ok_resp) == exists_ok_resp, { + assert forall |ex| #[trigger] exists_ok_resp.satisfied_by(ex) implies tla_exists(known_ok_resp).satisfied_by(ex) by { + let resp_msg = choose |resp_msg| { + &&& #[trigger] ex.head().in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) + &&& resp_msg.content.get_update_response().res.is_Ok() + &&& state_after_update(sub_resource, zookeeper, resp_msg.content.get_update_response().res.get_Ok_0(), ex.head().ongoing_reconciles()[zookeeper.object_ref()].local_state).is_Ok() + }; + assert(known_ok_resp(resp_msg).satisfied_by(ex)); + } + temp_pred_equality(tla_exists(known_ok_resp), exists_ok_resp); + }); + valid_implies_implies_leads_to(spec, match_and_ok_resp, exists_ok_resp); + leads_to_trans_n!(spec, pre, match_and_ok_resp, exists_ok_resp, lift_state(next_state)); + }); +} + +proof fn lemma_from_key_not_exists_to_receives_not_found_resp_at_after_get_resource_step( + spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage +) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), + ensures + spec.entails( + lift_state( + |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) + } + ).leads_to(lift_state( + |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) + } + )) + ), +{ + let pre = |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) + }; + let post = |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) + }; + let input = Some(req_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + assert(!resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0())); + if input.get_Some_0() == req_msg { + let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }); + } + }, + _ => {} + } + } + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) + implies post(s_prime) by { + let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }); + } + + ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, ZKCluster::handle_request(), pre, post + ); +} + +proof fn lemma_from_after_get_resource_step_to_after_create_resource_step( + spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage +) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), + ensures + spec.entails( + lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }).leads_to(lift_state(|s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) + })) + ), +{ + let pre = |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) + &&& resp_msg.content.get_get_response().res.is_Err() + &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() + }; + let post = |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) + }; + let input = (Some(resp_msg), Some(zookeeper.object_ref())); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) + &&& ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) + &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) + &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) + }; + + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), + lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), + lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)), + lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + assert(!resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0())); + }, + _ => {} + } + } + + ZKCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, ZKCluster::continue_reconcile(), pre, post + ); +} + +proof fn lemma_resource_state_matches_at_after_create_resource_step( + spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage +) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), + ensures + spec.entails( + lift_state( + |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) + } + ).leads_to( + lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) + ) + ), +{ + let pre = |s: ZKCluster| { + &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) + &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) + }; + let input = Some(req_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) + &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) + &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) + &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), + lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), + lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), + lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)) + ); + + let post = |s: ZKCluster| { + &&& sub_resource_state_matches(sub_resource, zookeeper)(s) + &&& at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)(s) + }; + + assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + let resp = ZKCluster::handle_create_request_msg(pending_msg, s.kubernetes_api_state).1; + assert(s_prime.in_flight().contains(resp)); + match sub_resource { + SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), + SubResource::ClientService => ServiceView::marshal_preserves_integrity(), + SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), + SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), + SubResource::StatefulSet => StatefulSetView::marshal_preserves_integrity(), + } + } + + assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + if resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0()) {} else {} + }, + _ => {}, + } + } + + ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, ZKCluster::handle_request(), pre, post + ); + temp_pred_equality( + lift_state(post), + lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) + ); +} + +proof fn lemma_from_key_exists_to_receives_ok_resp_at_after_get_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + ensures + spec.entails(lift_state(req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg)) + .leads_to(lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)))), +{ + let pre = req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg); + let post = at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper); + let resource_key = get_request(sub_resource, zookeeper).key; + let input = Some(req_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) + &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) + &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), + lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), + lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let req = input.get_Some_0(); + assert(!resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req)); + assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); + assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); + if input.get_Some_0() == req_msg { + let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_get_response().res.is_Ok() + &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] + }); + assert(post(s_prime)); + } + }, + _ => {} + } + } + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) + implies post(s_prime) by { + let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_get_response().res.is_Ok() + &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] + }); + } + + ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, ZKCluster::handle_request(), pre, post + ); +} + +proof fn lemma_resource_state_matches_at_after_update_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), + ensures + spec.entails(lift_state(req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg)) + .leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + .and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))))), +{ + let pre = req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg); + let resource_key = get_request(sub_resource, zookeeper).key; + let input = Some(req_msg); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) + &&& ZKCluster::desired_state_is(zookeeper)(s) + &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) + &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) + &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) + &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) + &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)(s) + &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) + &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), + lift_state(ZKCluster::desired_state_is(zookeeper)), + lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), + lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), + lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), + lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), + lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)), + lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)), + lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)) + ); + + let post = |s: ZKCluster| { + &&& sub_resource_state_matches(sub_resource, zookeeper)(s) + &&& at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)(s) + }; + + assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); + let resp = ZKCluster::handle_update_request_msg(pending_msg, s.kubernetes_api_state).1; + assert(s_prime.in_flight().contains(resp)); + match sub_resource { + SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), + SubResource::ClientService => ServiceView::marshal_preserves_integrity(), + SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), + SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), + _ => {} + } + } + + assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + assert(!resource_delete_request_msg(resource_key)(input.get_Some_0())); + assert(!resource_update_status_request_msg(resource_key)(input.get_Some_0())); + if resource_update_request_msg(resource_key)(input.get_Some_0()) {} else {} + }, + _ => {}, + } + } + + ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api(spec, input, stronger_next, ZKCluster::handle_request(), pre, post); + temp_pred_equality( + lift_state(post), + lift_state(sub_resource_state_matches(sub_resource, zookeeper)) + .and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) + ); +} + +proof fn lemma_from_after_get_resource_step_to_after_update_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), + ensures spec.entails(lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)).leads_to(lift_state(pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper)))), +{ + let pre = resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg); + let post = pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper); + let input = (Some(resp_msg), Some(zookeeper.object_ref())); + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& ZKCluster::crash_disabled()(s) + &&& ZKCluster::busy_disabled()(s) + &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) + &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) + &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) + &&& ZKCluster::desired_state_is(zookeeper)(s) + &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) + &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) + &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) + &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) + &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)(s) + }; + + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(ZKCluster::crash_disabled()), + lift_state(ZKCluster::busy_disabled()), + lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), + lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), + lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), + lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), + lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), + lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)), + lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)), + lift_state(ZKCluster::desired_state_is(zookeeper)) + ); + + assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let req = input.get_Some_0(); + assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); + assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); + if resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req) {} else {} + }, + _ => {}, + } + } + + ZKCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, + ZKCluster::continue_reconcile(), pre, post + ); +} + +pub proof fn lemma_resource_object_is_stable(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, p: TempPred) + requires + sub_resource != SubResource::StatefulSet, + spec.entails(p.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), + spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), + ensures spec.entails(p.leads_to(always(lift_state(sub_resource_state_matches(sub_resource, zookeeper))))), +{ + let post = sub_resource_state_matches(sub_resource, zookeeper); + let resource_key = get_request(sub_resource, zookeeper).key; + let stronger_next = |s, s_prime: ZKCluster| { + &&& ZKCluster::next()(s, s_prime) + &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) + &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) + &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) + &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)(s) + &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(ZKCluster::next()), + lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), + lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), + lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), + lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)), + lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)) + ); + + assert forall |s, s_prime: ZKCluster| post(s) && #[trigger] stronger_next(s, s_prime) implies post(s_prime) by { + let step = choose |step| ZKCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let req = input.get_Some_0(); + assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); + assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); + if resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req) {} else {} + }, + _ => {}, + } + match sub_resource { + SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), + SubResource::ClientService => ServiceView::marshal_preserves_integrity(), + SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), + SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), + _ => {} + } + } + + leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); +} + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs new file mode 100644 index 000000000..f13f3c2ae --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -0,0 +1,574 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, + stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::v_replica_set_controller::{ + model::reconciler::*, + //proof::{helper_invariants, liveness::terminate, predicate::*, resource::*}, + trusted::{liveness_theorem::*, spec_types::*, step::*}, +}; +use vstd::prelude::*; + +verus! { + +pub open spec fn assumption_and_invariants_of_all_phases(vrs: VReplicaSetView) -> TempPred { + invariants(vrs) + .and(always(lift_state(desired_state_is(vrs)))) + .and(true_pred() /* invariants_since_phase_i(vrs) */) + .and(true_pred() /* invariants_since_phase_ii(vrs) */) + .and(true_pred() /* invariants_since_phase_iii(vrs) */) + .and(true_pred() /* invariants_since_phase_iv(vrs) */) + .and(true_pred() /* invariants_since_phase_v(vrs) */) + .and(true_pred() /* invariants_since_phase_vi(vrs) */) + .and(true_pred() /* invariants_since_phase_vii(vrs) */) +} + +pub open spec fn invariants_since_phase_n(n: nat, vrs: VReplicaSetView) -> TempPred { + if n == 0 { + invariants(vrs).and(always(lift_state(desired_state_is(vrs)))) + } else if n == 1 { + true_pred() // invariants_since_phase_i(vrs) + } else if n == 2 { + true_pred() // invariants_since_phase_ii(vrs) + } else if n == 3 { + true_pred() // invariants_since_phase_iii(vrs) + } else if n == 4 { + true_pred() // invariants_since_phase_iv(vrs) + } else if n == 5 { + true_pred() // invariants_since_phase_v(vrs) + } else if n == 6 { + true_pred() // invariants_since_phase_vi(vrs) + } else if n == 7 { + true_pred() // invariants_since_phase_vii(vrs) + } else { + true_pred() + } +} + +pub open spec fn spec_before_phase_n(n: nat, vrs: VReplicaSetView) -> TempPred + decreases n, +{ + if n == 1 { + invariants(vrs).and(always(lift_state(desired_state_is(vrs)))) + } else if 2 <= n <= 8 { + spec_before_phase_n((n-1) as nat, vrs).and(invariants_since_phase_n((n-1) as nat, vrs)) + } else { + true_pred() + } +} + +// pub proof fn spec_of_previous_phases_entails_eventually_new_invariants(i: nat, zookeeper: ZookeeperClusterView) +// requires 1 <= i <= 7, +// ensures spec_before_phase_n(i, zookeeper).entails(true_pred().leads_to(invariants_since_phase_n(i, zookeeper))), +// { +// let spec = spec_before_phase_n(i, zookeeper); +// reveal_with_fuel(spec_before_phase_n, 8); +// if i == 1 { +// ZKCluster::lemma_true_leads_to_crash_always_disabled(spec); +// ZKCluster::lemma_true_leads_to_busy_always_disabled(spec); +// ZKCluster::lemma_true_leads_to_always_the_object_in_schedule_has_spec_and_uid_as(spec, zookeeper); +// leads_to_always_combine_n!( +// spec, +// true_pred(), +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::busy_disabled()), +// lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)) +// ); +// } else { +// terminate::reconcile_eventually_terminates(spec, zookeeper); +// if i == 2 { +// ZKCluster::lemma_true_leads_to_always_the_object_in_reconcile_has_spec_and_uid_as(spec, zookeeper); +// } else if i == 3 { +// helper_invariants::lemma_always_zookeeper_is_well_formed(spec, zookeeper); +// helper_invariants::lemma_eventually_always_every_resource_create_request_implies_at_after_create_resource_step_forall(spec, zookeeper); +// helper_invariants::lemma_eventually_always_object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr_forall(spec, zookeeper); +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)); +// let a_to_p_2 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr(sub_resource, zookeeper)); +// helper_invariants::lemma_eventually_always_every_zk_set_data_request_implies_at_after_update_zk_node_step(spec, zookeeper); +// helper_invariants::lemma_eventually_always_every_zk_create_node_request_implies_at_after_create_zk_node_step(spec, zookeeper); +// leads_to_always_combine_n!( +// spec, true_pred(), tla_forall(a_to_p_1), tla_forall(a_to_p_2), +// lift_state(helper_invariants::every_zk_set_data_request_implies_at_after_update_zk_node_step(zookeeper)), +// lift_state(helper_invariants::every_zk_create_node_request_implies_at_after_create_zk_node_step(zookeeper)) +// ); +// } else if i == 4 { +// helper_invariants::lemma_eventually_always_resource_object_only_has_owner_reference_pointing_to_current_cr_forall(spec, zookeeper); +// } else if i == 5 { +// helper_invariants::lemma_eventually_always_no_delete_resource_request_msg_in_flight_forall(spec, zookeeper); +// } else if i == 6 { +// helper_invariants::lemma_eventually_always_every_resource_update_request_implies_at_after_update_resource_step_forall(spec, zookeeper); +// helper_invariants::lemma_eventually_always_object_in_response_at_after_create_resource_step_is_same_as_etcd_forall(spec, zookeeper); +// helper_invariants::lemma_eventually_always_object_in_response_at_after_update_resource_step_is_same_as_etcd_forall(spec, zookeeper); +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)); +// leads_to_always_combine_n!( +// spec, true_pred(), +// tla_forall(a_to_p_1), lift_state(helper_invariants::object_in_response_at_after_create_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)), lift_state(helper_invariants::object_in_response_at_after_update_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)) +// ); +// } else if i == 7 { +// helper_invariants::lemma_eventually_always_cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated_forall(spec, zookeeper); +// } +// } +// } + +pub proof fn assumption_and_invariants_of_all_phases_is_stable(vrs: VReplicaSetView) + ensures + valid(stable(assumption_and_invariants_of_all_phases(vrs))), + valid(stable(invariants(vrs))), + forall |i: nat| 1 <= i <= 8 ==> valid(stable(#[trigger] spec_before_phase_n(i, vrs))), +{ + assume(false) + // reveal_with_fuel(spec_before_phase_n, 8); + // invariants_is_stable(zookeeper); + // always_p_is_stable(lift_state(desired_state_is(zookeeper))); + // invariants_since_phase_i_is_stable(zookeeper); + // invariants_since_phase_ii_is_stable(zookeeper); + // invariants_since_phase_iii_is_stable(zookeeper); + // invariants_since_phase_iv_is_stable(zookeeper); + // invariants_since_phase_v_is_stable(zookeeper); + // invariants_since_phase_vi_is_stable(zookeeper); + // invariants_since_phase_vii_is_stable(zookeeper); + // stable_and_n!( + // invariants(zookeeper), always(lift_state(desired_state_is(zookeeper))), + // invariants_since_phase_i(zookeeper), invariants_since_phase_ii(zookeeper), invariants_since_phase_iii(zookeeper), + // invariants_since_phase_iv(zookeeper), invariants_since_phase_v(zookeeper), invariants_since_phase_vi(zookeeper), + // invariants_since_phase_vii(zookeeper) + // ); +} + +// Next and all the wf conditions. +pub open spec fn next_with_wf() -> TempPred { + always(lift_action(VRSCluster::next())) + .and(tla_forall(|input| VRSCluster::kubernetes_api_next().weak_fairness(input))) + .and(tla_forall(|input| VRSCluster::external_api_next().weak_fairness(input))) + .and(tla_forall(|input| VRSCluster::controller_next().weak_fairness(input))) + .and(tla_forall(|input| VRSCluster::schedule_controller_reconcile().weak_fairness(input))) + .and(tla_forall(|input| VRSCluster::builtin_controllers_next().weak_fairness(input))) + .and(VRSCluster::disable_crash().weak_fairness(())) + .and(VRSCluster::disable_transient_failure().weak_fairness(())) +} + +// pub proof fn next_with_wf_is_stable() +// ensures valid(stable(next_with_wf())), +// { +// always_p_is_stable(lift_action(ZKCluster::next())); +// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::kubernetes_api_next()); +// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::external_api_next()); +// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::controller_next()); +// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::schedule_controller_reconcile()); +// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::builtin_controllers_next()); +// ZKCluster::action_weak_fairness_is_stable(ZKCluster::disable_crash()); +// ZKCluster::action_weak_fairness_is_stable(ZKCluster::disable_transient_failure()); +// stable_and_n!( +// always(lift_action(ZKCluster::next())), +// tla_forall(|input| ZKCluster::kubernetes_api_next().weak_fairness(input)), +// tla_forall(|input| ZKCluster::external_api_next().weak_fairness(input)), +// tla_forall(|input| ZKCluster::controller_next().weak_fairness(input)), +// tla_forall(|input| ZKCluster::schedule_controller_reconcile().weak_fairness(input)), +// tla_forall(|input| ZKCluster::builtin_controllers_next().weak_fairness(input)), +// ZKCluster::disable_crash().weak_fairness(()), +// ZKCluster::disable_transient_failure().weak_fairness(()) +// ); +// } + +/// This predicate combines all the possible actions (next), weak fairness and invariants that hold throughout the execution. +/// We name it invariants here because these predicates are never violated, thus they can all be seen as some kind of invariants. +/// +/// The final goal of our proof is to show init /\ invariants |= []desired_state_is(cr) ~> []current_state_matches(cr). +/// init /\ invariants is equivalent to init /\ next /\ weak_fairness, so we get cluster_spec() |= []desired_state_is(cr) ~> []current_state_matches(cr). +pub open spec fn invariants(vrs: VReplicaSetView) -> TempPred { + next_with_wf().and(derived_invariants_since_beginning(vrs)) +} + +// pub proof fn invariants_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants(zookeeper))), +// { +// next_with_wf_is_stable(); +// derived_invariants_since_beginning_is_stable(zookeeper); +// stable_and_n!( +// next_with_wf(), +// derived_invariants_since_beginning(zookeeper) +// ); +// } + +// The safety invariants that are required to prove liveness. +pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> TempPred { + true_pred() + // always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id())) + // .and(always(lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()))) + // .and(always(lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())))) + // .and(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))) + // .and(always(lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()))) + // .and(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))) + // .and(always(lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))) + // .and(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) + // .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper))))) + // .and(always(lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))) + // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))) + // .and(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1))))))) + // .and(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper))))) + // .and(always(lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)))) + // .and(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))) + // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) + // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) + // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) + // .and(always(tla_forall(|res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper))))) + // .and(always(tla_forall(|res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key))))) + // .and(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))) + // .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper))))) +} + +// pub proof fn derived_invariants_since_beginning_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(derived_invariants_since_beginning(zookeeper))), +// { +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)); +// let a_to_p_2 = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); +// let a_to_p_3 = |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)); +// let a_to_p_4 = |res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper)); +// let a_to_p_5 = |res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key)); +// let a_to_p_6 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)); +// stable_and_always_n!( +// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), +// lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()), +// lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())), +// lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), +// lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()), +// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), +// lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), +// lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), +// tla_forall(a_to_p_1), +// lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))), +// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))), +// tla_forall(a_to_p_2), +// tla_forall(a_to_p_3), +// lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)), +// lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), +// lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), +// lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), +// lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), +// tla_forall(a_to_p_4), +// tla_forall(a_to_p_5), +// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), +// tla_forall(a_to_p_6) +// ); +// } + +// /// The first notable phase comes when crash and k8s busy are always disabled and the object in schedule always has the same +// /// spec and uid as the cr we provide. +// /// +// /// Note that don't try to find any connections between those invariants -- they are put together because they don't have to +// /// wait for another of them to first be satisfied. +// pub open spec fn invariants_since_phase_i(zookeeper: ZookeeperClusterView) -> TempPred { +// always(lift_state(ZKCluster::crash_disabled())) +// .and(always(lift_state(ZKCluster::busy_disabled()))) +// .and(always(lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)))) +// } + +// pub proof fn invariants_since_phase_i_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_i(zookeeper))), +// { +// stable_and_always_n!( +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::busy_disabled()), +// lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)) +// ); +// } + +// /// For now, phase II only contains one invariant, which is the object in reconcile has the same spec and uid as zookeeper. +// /// +// /// It is alone because it relies on the invariant the_object_in_schedule_has_spec_and_uid_as (in phase I) and every invariant +// /// in phase III relies on it. +// pub open spec fn invariants_since_phase_ii(zookeeper: ZookeeperClusterView) -> TempPred { +// always(lift_state(ZKCluster::the_object_in_reconcile_has_spec_and_uid_as(zookeeper))) +// } + + +// pub proof fn invariants_since_phase_ii_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_ii(zookeeper))), +// { +// always_p_is_stable(lift_state(ZKCluster::the_object_in_reconcile_has_spec_and_uid_as(zookeeper))); +// } + +// pub open spec fn invariants_since_phase_iii(zookeeper: ZookeeperClusterView) -> TempPred { +// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))) +// .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr(sub_resource, zookeeper))))) +// .and(always(lift_state(helper_invariants::every_zk_set_data_request_implies_at_after_update_zk_node_step(zookeeper)))) +// .and(always(lift_state(helper_invariants::every_zk_create_node_request_implies_at_after_create_zk_node_step(zookeeper)))) +// } + +// pub proof fn invariants_since_phase_iii_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_iii(zookeeper))), +// { +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)); +// let a_to_p_2 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr(sub_resource, zookeeper)); +// stable_and_always_n!(tla_forall(a_to_p_1), tla_forall(a_to_p_2), +// lift_state(helper_invariants::every_zk_set_data_request_implies_at_after_update_zk_node_step(zookeeper)), +// lift_state(helper_invariants::every_zk_create_node_request_implies_at_after_create_zk_node_step(zookeeper)) +// ); +// } + +// /// Invariants since this phase ensure that certain objects only have owner references that point to current cr. +// /// To have these invariants, we first need the invariant that evert create/update request make/change the object in the +// /// expected way. +// pub open spec fn invariants_since_phase_iv(zookeeper: ZookeeperClusterView) -> TempPred { +// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))) +// } + +// pub proof fn invariants_since_phase_iv_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_iv(zookeeper))), +// { +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)); +// always_p_is_stable(tla_forall(a_to_p_1)); +// } + +// /// Invariants since phase V rely on the invariants since phase IV. When the objects starts to always have owner reference +// /// pointing to current cr, it will never be recycled by the garbage collector. Plus, the reconciler itself never tries to +// /// delete this object, so we can have the invariants saying that no delete request messages will be in flight. +// pub open spec fn invariants_since_phase_v(zookeeper: ZookeeperClusterView) -> TempPred { +// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))) +// } + +// pub proof fn invariants_since_phase_v_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_v(zookeeper))), +// { +// always_p_is_stable(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))); +// } + +// pub open spec fn invariants_since_phase_vi(zookeeper: ZookeeperClusterView) -> TempPred { +// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))) +// .and(always(lift_state(helper_invariants::object_in_response_at_after_create_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)))) +// .and(always(lift_state(helper_invariants::object_in_response_at_after_update_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)))) +// } + +// pub proof fn invariants_since_phase_vi_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_vi(zookeeper))), +// { +// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)); +// stable_and_always_n!( +// tla_forall(a_to_p_1), +// lift_state(helper_invariants::object_in_response_at_after_create_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)), +// lift_state(helper_invariants::object_in_response_at_after_update_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)) +// ); +// } + +// pub open spec fn invariants_since_phase_vii(zookeeper: ZookeeperClusterView) -> TempPred { +// always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper))) +// } + +// pub proof fn invariants_since_phase_vii_is_stable(zookeeper: ZookeeperClusterView) +// ensures valid(stable(invariants_since_phase_vii(zookeeper))), +// { +// always_p_is_stable(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper))); +// } + +// pub proof fn lemma_always_for_all_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures spec.entails(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1))))))), +// { +// // TODO (xudong): investigate the performance of this lemma +// // Somehow the reasoning inside the assert forall block below is very slow (takes more than 8 seconds!) +// // I suspect it is related to the precondition of lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state +// let a_to_p = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); +// assert_by(spec.entails(always(tla_forall(a_to_p))), { +// assert forall |step: (ActionKind, SubResource)| spec.entails(always(#[trigger] a_to_p(step))) by { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state( +// spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) +// ); +// } +// spec_entails_always_tla_forall(spec, a_to_p); +// }); +// } + +// pub proof fn lemma_always_for_after_exists_stateful_set_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures +// spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet)); +// } + +// pub proof fn lemma_always_for_after_exists_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures +// spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode)); +// } + +// pub proof fn lemma_always_for_after_create_zk_parent_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode)); +// } + +// pub proof fn lemma_always_for_after_create_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode)); +// } + +// pub proof fn lemma_always_for_after_update_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode)); +// } + +// pub proof fn lemma_always_for_after_update_status_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) +// requires +// spec.entails(lift_state(ZKCluster::init())), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))), +// { +// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus)); +// } + +pub proof fn sm_spec_entails_all_invariants(vrs: VReplicaSetView) + ensures cluster_spec().entails(derived_invariants_since_beginning(vrs)), +{ + assume(false); + // let spec = cluster_spec(); + // // Adding two assertions to make the verification faster because all the lemmas below require the two preconditions. + // // And then the verifier doesn't have to infer it every time applying those lemmas. + // assert(spec.entails(lift_state(ZKCluster::init()))); + // assert(spec.entails(always(lift_action(ZKCluster::next())))); + // ZKCluster::lemma_always_every_in_flight_msg_has_unique_id(spec); + // ZKCluster::lemma_always_object_in_ok_get_response_has_smaller_rv_than_etcd(spec); + // ZKCluster::lemma_always_every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(spec, zookeeper.object_ref()); + // ZKCluster::lemma_always_pending_req_of_key_is_unique_with_unique_id(spec, zookeeper.object_ref()); + // ZKCluster::lemma_always_every_in_flight_msg_has_lower_id_than_allocator(spec); + // ZKCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + // ZKCluster::lemma_always_each_scheduled_object_has_consistent_key_and_valid_metadata(spec); + // ZKCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + // let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)); + // assert_by(spec.entails(always(tla_forall(a_to_p_1))), { + // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_1(sub_resource))) by { + // helper_invariants::lemma_always_resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(spec, sub_resource, zookeeper); + // } + // spec_entails_always_tla_forall(spec, a_to_p_1); + // }); + // ZKCluster::lemma_always_no_pending_req_msg_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init)); + + // // Different from other a_to_p_x, we encapsulate a_to_p_2 inside the lemma below because we find its reasoning is + // // surprisingly slow in this context. Encapsulating the reasoning reduces the verification time of this function + // // from more than 40 seconds to 2 seconds. + // let a_to_p_2 = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); + // lemma_always_for_all_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // // lemma_always_for_all_non_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_exists_stateful_set_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_exists_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_create_zk_parent_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_create_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_update_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + // lemma_always_for_after_update_status_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); + + // let a_to_p_3 = |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)); + // assert_by(spec.entails(always(tla_forall(a_to_p_3))), { + // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_3(sub_resource))) by { + // helper_invariants::lemma_always_no_update_status_request_msg_in_flight_of_except_stateful_set(spec, sub_resource, zookeeper); + // } + // spec_entails_always_tla_forall(spec, a_to_p_3); + // }); + // helper_invariants::lemma_always_no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(spec, zookeeper); + // helper_invariants::lemma_always_the_object_in_reconcile_satisfies_state_validation(spec, zookeeper.object_ref()); + // ZKCluster::lemma_always_key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); + // ZKCluster::lemma_always_key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); + // ZKCluster::lemma_always_key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); + // let a_to_p_4 = |res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper)); + // assert_by(spec.entails(always(tla_forall(a_to_p_4))), { + // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_4(sub_resource))) by { + // helper_invariants::lemma_always_response_at_after_get_resource_step_is_resource_get_response(spec, sub_resource, zookeeper); + // } + // spec_entails_always_tla_forall(spec, a_to_p_4); + // }); + // let a_to_p_5 = |res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key)); + // assert_by(spec.entails(always(tla_forall(a_to_p_5))), { + // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_5(sub_resource))) by { + // ZKCluster::lemma_always_object_in_ok_get_resp_is_same_as_etcd_with_same_rv(spec, get_request(sub_resource, zookeeper).key); + // } + // spec_entails_always_tla_forall(spec, a_to_p_5); + // }); + // helper_invariants::lemma_always_stateful_set_in_etcd_satisfies_unchangeable(spec, zookeeper); + // let a_to_p_6 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)); + // assert_by(spec.entails(always(tla_forall(a_to_p_6))), { + // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_6(sub_resource))) by { + // helper_invariants::lemma_always_object_in_etcd_satisfies_unchangeable(spec, sub_resource, zookeeper); + // } + // spec_entails_always_tla_forall(spec, a_to_p_6); + // }); + + // entails_always_and_n!( + // spec, + // lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), + // lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()), + // lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())), + // lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), + // lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()), + // lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), + // lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), + // lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + // tla_forall(a_to_p_1), + // lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))), + // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))), + // tla_forall(a_to_p_2), + // tla_forall(a_to_p_3), + // lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)), + // lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), + // lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), + // lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), + // lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), + // tla_forall(a_to_p_4), + // tla_forall(a_to_p_5), + // lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), + // tla_forall(a_to_p_6) + // ); +} + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs new file mode 100644 index 000000000..132ea0022 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs @@ -0,0 +1,531 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{map_lib::*, string_view::*}; +// use crate::zookeeper_controller::{ +// model::{reconciler::*, resource::*}, +// proof::{helper_invariants, liveness::resource_match::*, predicate::*, resource::*}, +// trusted::{spec_types::*, step::*}, +// }; +use vstd::{prelude::*, string::*}; + +verus! { + +// pub proof fn lemma_from_after_get_stateful_set_step_to_stateful_set_matches( +// spec: TempPred, zookeeper: ZookeeperClusterView +// ) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), +// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), +// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), +// spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), +// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(SubResource::ConfigMap, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), +// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), +// ensures spec.entails(lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)).leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)))), +// { +// lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches(spec, SubResource::StatefulSet, zookeeper); +// lemma_from_after_get_stateful_set_step_and_key_exists_to_stateful_set_matches(spec, zookeeper); +// let key_not_exists = lift_state(|s: ZKCluster| { +// &&& !s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }); +// let key_exists = lift_state(|s: ZKCluster| { +// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }); +// or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))); +// temp_pred_equality( +// key_not_exists.or(key_exists), lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)) +// ); +// } + +// proof fn lemma_from_after_get_stateful_set_step_and_key_exists_to_stateful_set_matches( +// spec: TempPred, zookeeper: ZookeeperClusterView +// ) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), +// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), +// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), +// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), +// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), +// ensures +// spec.entails( +// lift_state(|s: ZKCluster| { +// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }).leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))) +// ), +// { +// let sts_key = get_request(SubResource::StatefulSet, zookeeper).key; +// let pre = lift_state(|s: ZKCluster| { +// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }); +// let stateful_set_matches = lift_state(|s: ZKCluster| { +// &&& s.resources().contains_key(sts_key) +// &&& sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }); +// let stateful_set_not_matches = lift_state(|s: ZKCluster| { +// &&& s.resources().contains_key(sts_key) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) +// }); +// let post = lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)); + +// assert_by(spec.entails(stateful_set_matches.leads_to(post)), { +// valid_implies_implies_leads_to(spec, stateful_set_matches, post); +// }); + +// assert_by(spec.entails(stateful_set_not_matches.leads_to(post)), { +// let pre1 = |req_msg| lift_state(|s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }); +// let post1 = lift_state(|s: ZKCluster| { +// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }); +// assert forall |req_msg| spec.entails(#[trigger] pre1(req_msg).leads_to(post1)) by { +// lemma_from_key_exists_to_receives_ok_resp_at_after_get_stateful_set_step(spec, zookeeper, req_msg); +// } +// leads_to_exists_intro(spec, pre1, post1); +// assert_by(tla_exists(pre1) == stateful_set_not_matches, { +// assert forall |ex| #[trigger] stateful_set_not_matches.satisfied_by(ex) +// implies tla_exists(pre1).satisfied_by(ex) by { +// let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); +// assert(pre1(req_msg).satisfied_by(ex)); +// } +// temp_pred_equality(tla_exists(pre1), stateful_set_not_matches); +// }); + +// let pre2 = |resp_msg| lift_state(|s: ZKCluster| { +// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }); +// let post2 = lift_state(|s: ZKCluster| { +// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }); +// assert forall |resp_msg| spec.entails(#[trigger] pre2(resp_msg).leads_to(post2)) by { +// lemma_from_after_get_stateful_set_step_to_after_update_stateful_set_step(spec, zookeeper, resp_msg); +// } +// leads_to_exists_intro(spec, pre2, post2); +// assert_by(tla_exists(pre2) == post1, { +// assert forall |ex| #[trigger] post1.satisfied_by(ex) +// implies tla_exists(pre2).satisfied_by(ex) by { +// let resp_msg = choose |resp_msg| { +// &&& #[trigger] ex.head().in_flight().contains(resp_msg) +// &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) +// &&& resp_msg.content.get_get_response().res.is_Ok() +// &&& resp_msg.content.get_get_response().res.get_Ok_0() == ex.head().resources()[sts_key] +// }; +// assert(pre2(resp_msg).satisfied_by(ex)); +// } +// temp_pred_equality(tla_exists(pre2), post1); +// }); + +// let pre3 = |req_msg| lift_state(|s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }); +// assert forall |req_msg| spec.entails(#[trigger] pre3(req_msg).leads_to(post)) by { +// lemma_stateful_set_state_matches_at_after_update_stateful_set_step(spec, zookeeper, req_msg); +// } +// leads_to_exists_intro(spec, pre3, post); +// assert_by(tla_exists(pre3) == post2, { +// assert forall |ex| #[trigger] post2.satisfied_by(ex) +// implies tla_exists(pre3).satisfied_by(ex) by { +// let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); +// assert(pre3(req_msg).satisfied_by(ex)); +// } +// temp_pred_equality(tla_exists(pre3), post2); +// }); + +// leads_to_trans_n!(spec, stateful_set_not_matches, post1, post2, post); +// }); + +// or_leads_to_combine_temp(spec, stateful_set_matches, stateful_set_not_matches, post); +// temp_pred_equality(stateful_set_matches.or(stateful_set_not_matches), pre); +// } + +// proof fn lemma_from_key_exists_to_receives_ok_resp_at_after_get_stateful_set_step( +// spec: TempPred, zookeeper: ZookeeperClusterView, req_msg: ZKMessage +// ) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), +// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), +// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), +// ensures +// spec.entails( +// lift_state(|s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }) +// .leads_to(lift_state(|s: ZKCluster| { +// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// })) +// ), +// { +// let pre = |s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }; +// let post = |s: ZKCluster| { +// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }; +// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; +// let input = Some(req_msg); +// let stronger_next = |s, s_prime: ZKCluster| { +// &&& ZKCluster::next()(s, s_prime) +// &&& ZKCluster::crash_disabled()(s) +// &&& ZKCluster::busy_disabled()(s) +// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) +// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) +// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) +// }; +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), +// lift_action(ZKCluster::next()), +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::busy_disabled()), +// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), +// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), +// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) +// ); + +// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { +// let step = choose |step| ZKCluster::next_step(s, s_prime, step); +// match step { +// Step::ApiServerStep(input) => { +// let req = input.get_Some_0(); +// assert(!resource_delete_request_msg(resource_key)(req)); +// assert(!resource_update_request_msg(resource_key)(req)); +// assert(!resource_update_status_request_msg(resource_key)(req)); +// if input.get_Some_0() == req_msg { +// let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; +// assert({ +// &&& s_prime.in_flight().contains(resp_msg) +// &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) +// &&& resp_msg.content.get_get_response().res.is_Ok() +// &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] +// }); +// assert(post(s_prime)); +// } +// }, +// _ => {} +// } +// } + +// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) +// implies post(s_prime) by { +// let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; +// assert({ +// &&& s_prime.in_flight().contains(resp_msg) +// &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) +// &&& resp_msg.content.get_get_response().res.is_Ok() +// &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] +// }); +// } + +// ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( +// spec, input, stronger_next, ZKCluster::handle_request(), pre, post +// ); +// } + +// proof fn lemma_from_after_get_stateful_set_step_to_after_update_stateful_set_step( +// spec: TempPred, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage +// ) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), +// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), +// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), +// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), +// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), +// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), +// ensures +// spec.entails( +// lift_state(|s: ZKCluster| { +// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }) +// .leads_to(lift_state(|s: ZKCluster| { +// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// })) +// ), +// { +// let pre = |s: ZKCluster| { +// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }; +// let post = |s: ZKCluster| { +// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }; +// let input = (Some(resp_msg), Some(zookeeper.object_ref())); +// let stronger_next = |s, s_prime: ZKCluster| { +// &&& ZKCluster::next()(s, s_prime) +// &&& ZKCluster::crash_disabled()(s) +// &&& ZKCluster::busy_disabled()(s) +// &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) +// &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) +// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) +// &&& ZKCluster::desired_state_is(zookeeper)(s) +// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) +// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) +// &&& helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)(s) +// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) +// }; + +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), +// lift_action(ZKCluster::next()), +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::busy_disabled()), +// lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), +// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), +// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), +// lift_state(ZKCluster::desired_state_is(zookeeper)), +// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), +// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), +// lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), +// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) +// ); + +// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { +// let step = choose |step| ZKCluster::next_step(s, s_prime, step); +// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; +// match step { +// Step::ApiServerStep(input) => { +// let req = input.get_Some_0(); +// assert(!resource_delete_request_msg(resource_key)(req)); +// assert(!resource_update_request_msg(resource_key)(req)); +// assert(!resource_update_status_request_msg(resource_key)(req)); +// }, +// _ => {} +// } +// } +// ZKCluster::lemma_pre_leads_to_post_by_controller( +// spec, input, stronger_next, +// ZKCluster::continue_reconcile(), pre, post +// ); +// } + +// proof fn lemma_stateful_set_state_matches_at_after_update_stateful_set_step(spec: TempPred, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) +// requires +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), +// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), +// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), +// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), +// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), +// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), +// ensures +// spec.entails( +// lift_state(|s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }) +// .leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))) +// ), +// { +// let pre = |s: ZKCluster| { +// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) +// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) +// }; +// let post = sub_resource_state_matches(SubResource::StatefulSet, zookeeper); + +// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; +// let input = Some(req_msg); +// let stronger_next = |s, s_prime: ZKCluster| { +// &&& ZKCluster::next()(s, s_prime) +// &&& ZKCluster::crash_disabled()(s) +// &&& ZKCluster::busy_disabled()(s) +// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) +// &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) +// &&& ZKCluster::desired_state_is(zookeeper)(s) +// &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) +// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) +// &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) +// &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)(s) +// }; +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), +// lift_action(ZKCluster::next()), +// lift_state(ZKCluster::crash_disabled()), +// lift_state(ZKCluster::busy_disabled()), +// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), +// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), +// lift_state(ZKCluster::desired_state_is(zookeeper)), +// lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), +// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), +// lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), +// lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)) +// ); + +// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) +// implies post(s_prime) by { +// let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); +// let resp = ZKCluster::handle_update_request_msg(pending_msg, s.kubernetes_api_state).1; +// assert(s_prime.in_flight().contains(resp)); +// StatefulSetView::marshal_preserves_integrity(); +// } + +// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { +// let step = choose |step| ZKCluster::next_step(s, s_prime, step); +// match step { +// Step::ApiServerStep(input) => { +// let req = input.get_Some_0(); +// assert(!resource_delete_request_msg(resource_key)(req)); +// assert(!resource_update_status_request_msg(resource_key)(req)); +// if resource_update_request_msg(resource_key)(req) {} else {} +// }, +// _ => {} +// } +// } + +// ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api(spec, input, stronger_next, ZKCluster::handle_request(), pre, post); +// } + +// pub proof fn lemma_stateful_set_is_stable( +// spec: TempPred, zookeeper: ZookeeperClusterView, p: TempPred +// ) +// requires +// spec.entails(p.leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_action(ZKCluster::next()))), +// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), +// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), +// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), +// ensures spec.entails(p.leads_to(always(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))))), +// { +// let post = sub_resource_state_matches(SubResource::StatefulSet, zookeeper); +// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; +// let stronger_next = |s, s_prime: ZKCluster| { +// &&& ZKCluster::next()(s, s_prime) +// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)(s) +// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) +// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) +// }; +// combine_spec_entails_always_n!( +// spec, lift_action(stronger_next), +// lift_action(ZKCluster::next()), +// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)), +// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), +// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) +// ); + +// assert forall |s, s_prime: ZKCluster| post(s) && #[trigger] stronger_next(s, s_prime) implies post(s_prime) by { +// StatefulSetView::marshal_preserves_integrity(); +// let step = choose |step| ZKCluster::next_step(s, s_prime, step); +// match step { +// Step::ApiServerStep(input) => { +// let req = input.get_Some_0(); +// assert(!resource_delete_request_msg(resource_key)(req)); +// if resource_update_request_msg(resource_key)(req) {} else {} +// }, +// _ => {} +// } +// } + +// leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); +// } + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs new file mode 100644 index 000000000..c08187ca2 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs @@ -0,0 +1,250 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::EmptyAPI; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, resource::*, stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::reconciler::spec::reconciler::*; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::zookeeper_controller::{ + model::reconciler::*, + proof::predicate::*, + trusted::{spec_types::*, step::*}, +}; +use vstd::prelude::*; + +verus! { + +pub proof fn reconcile_eventually_terminates(spec: TempPred, zookeeper: ZookeeperClusterView) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + spec.entails(always(lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), |s: ZookeeperReconcileState| s.reconcile_step == ZookeeperReconcileStep::Init)))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))), + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))), + spec.entails(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) + ))))), + ensures spec.entails(true_pred().leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), +{ + assert forall |action: ActionKind, sub_resource: SubResource| #![auto] + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource)) + )))) by { + always_tla_forall_apply::( + spec, |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) + )), (action, sub_resource) + ); + } + let reconcile_idle = |s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }; + + // First, prove that reconcile_done \/ reconcile_error \/ reconcile_ide ~> reconcile_idle. + // Here we simply apply a cluster lemma which uses the wf1 of end_reconcile action. + ZKCluster::lemma_reconcile_error_leads_to_reconcile_idle(spec, zookeeper.object_ref()); + ZKCluster::lemma_reconcile_done_leads_to_reconcile_idle(spec, zookeeper.object_ref()); + temp_pred_equality(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), lift_state(ZKCluster::reconciler_reconcile_done(zookeeper.object_ref()))); + temp_pred_equality(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)), lift_state(ZKCluster::reconciler_reconcile_error(zookeeper.object_ref()))); + valid_implies_implies_leads_to(spec, lift_state(reconcile_idle), lift_state(reconcile_idle)); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_state_pred(zookeeper, ZookeeperReconcileStep::Done, ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus), + at_step1_or_step2_closure(ZookeeperReconcileStep::Done, ZookeeperReconcileStep::Error) + ); + lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::StatefulSet, ZookeeperReconcileStep::AfterUpdateStatus); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode), + at_step1_or_step2_closure(after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) + ); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode), + at_step1_or_step2_closure(after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) + ); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode, ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode), + at_step1_or_step2_closure(ZookeeperReconcileStep::AfterCreateZKNode, ZookeeperReconcileStep::Error) + ); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_or_step3_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode, ZookeeperReconcileStep::AfterCreateZKParentNode, ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKParentNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode), + at_step1_or_step2_or_step3_closure(ZookeeperReconcileStep::AfterUpdateZKNode, ZookeeperReconcileStep::AfterCreateZKParentNode, ZookeeperReconcileStep::Error) + ); + + or_leads_to_combine_and_equality!(spec, + lift_state(at_step1_or_step2_or_step3_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode)), lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( + spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet), + at_step1_or_step2_or_step3_closure(ZookeeperReconcileStep::AfterExistsZKNode, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) + ); + + lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::ConfigMap, ZookeeperReconcileStep::AfterExistsStatefulSet); + lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::AdminServerService, after_get_k_request_step(SubResource::ConfigMap)); + lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::ClientService, after_get_k_request_step(SubResource::AdminServerService)); + lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::HeadlessService, after_get_k_request_step(SubResource::ClientService)); + + ZKCluster::lemma_from_init_state_to_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(ZookeeperReconcileStep::Init), at_step_closure(after_get_k_request_step(SubResource::HeadlessService))); + + // Finally, combine all cases. + or_leads_to_combine_and_equality!( + spec, + true_pred(), + lift_state(reconcile_idle), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Init)), + lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::HeadlessService)), + lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::ClientService)), + lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::AdminServerService)), + lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::ConfigMap)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsStatefulSet)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKParentNode)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode)), + lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::StatefulSet)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateStatus)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(reconcile_idle) + ); +} + +pub open spec fn at_step1_or_step2_closure(step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep) -> spec_fn(ZookeeperReconcileState) -> bool { + |s: ZookeeperReconcileState| s.reconcile_step == step1 || s.reconcile_step == step2 +} + +pub open spec fn at_step1_or_step2_or_step3_closure(step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep, step3: ZookeeperReconcileStep) -> spec_fn(ZookeeperReconcileState) -> bool { + |s: ZookeeperReconcileState| s.reconcile_step == step1 || s.reconcile_step == step2 || s.reconcile_step == step3 +} + +pub open spec fn at_step_state_pred(zookeeper: ZookeeperClusterView, step: ZookeeperReconcileStep) -> StatePred { + ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step_closure(step)) +} + +pub open spec fn at_step1_or_step2_state_pred(zookeeper: ZookeeperClusterView, step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep) -> StatePred { + ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step1_or_step2_closure(step1, step2)) +} + +pub open spec fn at_step1_or_step2_or_step3_state_pred(zookeeper: ZookeeperClusterView, step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep, step3: ZookeeperReconcileStep) -> StatePred { + ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step1_or_step2_or_step3_closure(step1, step2, step3)) +} + +pub open spec fn state_pred_regarding_sub_resource(zookeeper: ZookeeperClusterView, sub_resource: SubResource) -> StatePred { + ZKCluster::at_expected_reconcile_states( + zookeeper.object_ref(), + |s: ZookeeperReconcileState| s.reconcile_step.is_AfterKRequestStep() && s.reconcile_step.get_AfterKRequestStep_1() == sub_resource + ) +} + +proof fn lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle( + spec: TempPred, zookeeper: ZookeeperClusterView, sub_resource: SubResource, next_step: ZookeeperReconcileStep +) + requires + spec.entails(always(lift_action(ZKCluster::next()))), + spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(ZKCluster::crash_disabled()))), + spec.entails(always(lift_state(ZKCluster::busy_disabled()))), + spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), + // Ensures that after get/create/update the sub resource, there is always a pending request or matched response + // in flight so that the reconciler can enter the next state. + forall |action: ActionKind| #![auto] + spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource) + ))))), + // Ensures that after successfully creating or updating the sub resource, the reconcile will go to after get next + // sub resource step. + next_resource_after(sub_resource) == next_step, + spec.entails(lift_state(at_step_state_pred(zookeeper, next_step)) + .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), + spec.entails(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)) + .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), + ensures + spec.entails(lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(sub_resource))) + .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), + spec.entails(lift_state(state_pred_regarding_sub_resource(zookeeper, sub_resource)) + .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), +{ + let state_after_create_or_update = |s: ZookeeperReconcileState| { + s.reconcile_step == next_step + || s.reconcile_step == ZookeeperReconcileStep::Error + }; + or_leads_to_combine_and_equality!( + spec, lift_state(ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), state_after_create_or_update)), + lift_state(at_step_state_pred(zookeeper, next_step)), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_create_k_request_step(sub_resource)), state_after_create_or_update); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_update_k_request_step(sub_resource)), state_after_create_or_update); + + let state_after_get = |s: ZookeeperReconcileState| { + s.reconcile_step == after_create_k_request_step(sub_resource) + || s.reconcile_step == after_update_k_request_step(sub_resource) + || s.reconcile_step == ZookeeperReconcileStep::Error + }; + or_leads_to_combine_and_equality!( + spec, lift_state(ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), state_after_get)), + lift_state(at_step_state_pred(zookeeper, after_create_k_request_step(sub_resource))), + lift_state(at_step_state_pred(zookeeper, after_update_k_request_step(sub_resource))), + lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); + lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) + ); + ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_get_k_request_step(sub_resource)), state_after_get); + or_leads_to_combine_and_equality!( + spec, lift_state(state_pred_regarding_sub_resource(zookeeper, sub_resource)), + lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(sub_resource))), + lift_state(at_step_state_pred(zookeeper, after_create_k_request_step(sub_resource))), + lift_state(at_step_state_pred(zookeeper, after_update_k_request_step(sub_resource))); + lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) + ); +} + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/mod.rs b/src/controller_examples/v_replica_set_controller/proof/mod.rs index e69de29bb..7a14a3b6f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/mod.rs +++ b/src/controller_examples/v_replica_set_controller/proof/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +pub mod helper_invariants; +pub mod liveness; +pub mod predicate; diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs new file mode 100644 index 000000000..39eb97c51 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -0,0 +1,23 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, prelude::*, resource::*, stateful_set::*, +}; +use crate::kubernetes_cluster::proof::controller_runtime::*; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::defs::*; +use crate::v_replica_set_controller::model::{reconciler::*}; +use crate::v_replica_set_controller::trusted::{ + liveness_theorem::*, spec_types::*, step::*, +}; +use vstd::prelude::*; + +verus! { + +} From 88b2e601bf3aad6575dd6a87856c7904be7696d3 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 7 Aug 2024 20:22:55 -0500 Subject: [PATCH 02/22] Add essential boundary lemmas Signed-off-by: Cody Rivera --- .../model/reconciler.rs | 32 +- .../proof/helper_invariants/mod.rs | 7 +- .../proof/helper_invariants/owner_ref.rs | 29 - .../proof/helper_invariants/unchangeable.rs | 31 - .../proof/helper_invariants/validation.rs | 30 - .../proof/liveness/mod.rs | 5 +- .../proof/liveness/proof.rs | 223 +--- .../proof/liveness/resource_match.rs | 1016 ++--------------- .../proof/liveness/spec.rs | 468 +------- .../proof/liveness/stateful_set_match.rs | 531 --------- .../proof/liveness/terminate.rs | 236 +--- .../proof/predicate.rs | 94 ++ .../trusted/exec_types.rs | 2 +- .../trusted/spec_types.rs | 2 +- .../v_replica_set_controller/trusted/step.rs | 26 - 15 files changed, 223 insertions(+), 2509 deletions(-) delete mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs delete mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs delete mode 100644 src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs delete mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs diff --git a/src/controller_examples/v_replica_set_controller/model/reconciler.rs b/src/controller_examples/v_replica_set_controller/model/reconciler.rs index 6cc708fbd..a2a529def 100644 --- a/src/controller_examples/v_replica_set_controller/model/reconciler.rs +++ b/src/controller_examples/v_replica_set_controller/model/reconciler.rs @@ -34,21 +34,21 @@ impl Reconciler for VReplicaSetReconciler { pub open spec fn reconcile_init_state() -> VReplicaSetReconcileState { VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::Init, + reconcile_step: VReplicaSetReconcileStep::Init, filtered_pods: None, } } pub open spec fn reconcile_done(state: VReplicaSetReconcileState) -> bool { match state.reconcile_step { - VReplicaSetReconcileStepView::Done => true, + VReplicaSetReconcileStep::Done => true, _ => false, } } pub open spec fn reconcile_error(state: VReplicaSetReconcileState) -> bool { match state.reconcile_step { - VReplicaSetReconcileStepView::Error => true, + VReplicaSetReconcileStep::Error => true, _ => false, } } @@ -58,18 +58,18 @@ pub open spec fn reconcile_core( ) -> (VReplicaSetReconcileState, Option>) { let namespace = v_replica_set.metadata.namespace.unwrap(); match &state.reconcile_step { - VReplicaSetReconcileStepView::Init => { + VReplicaSetReconcileStep::Init => { let req = APIRequest::ListRequest(ListRequest { kind: PodView::kind(), namespace: namespace, }); let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::AfterListPods, + reconcile_step: VReplicaSetReconcileStep::AfterListPods, ..state }; (state_prime, Some(RequestView::KRequest(req))) }, - VReplicaSetReconcileStepView::AfterListPods => { + VReplicaSetReconcileStep::AfterListPods => { if !(resp_o.is_Some() && resp_o.get_Some_0().is_KResponse() && resp_o.get_Some_0().get_KResponse_0().is_ListResponse() && resp_o.get_Some_0().get_KResponse_0().get_ListResponse_0().res.is_ok()) { @@ -89,7 +89,7 @@ pub open spec fn reconcile_core( let desired_replicas: usize = replicas as usize; if filtered_pods.len() == desired_replicas { let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::Done, + reconcile_step: VReplicaSetReconcileStep::Done, ..state }; (state_prime, None) @@ -101,7 +101,7 @@ pub open spec fn reconcile_core( obj: pod.marshal(), }); let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::AfterCreatePod((diff - 1) as nat), + reconcile_step: VReplicaSetReconcileStep::AfterCreatePod((diff - 1) as usize), ..state }; (state_prime, Some(RequestView::KRequest(req))) @@ -119,7 +119,7 @@ pub open spec fn reconcile_core( } }); let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::AfterDeletePod((diff - 1) as nat), + reconcile_step: VReplicaSetReconcileStep::AfterDeletePod((diff - 1) as usize), filtered_pods: Some(filtered_pods), ..state }; @@ -130,7 +130,7 @@ pub open spec fn reconcile_core( } } }, - VReplicaSetReconcileStepView::AfterCreatePod(diff) => { + VReplicaSetReconcileStep::AfterCreatePod(diff) => { let diff = *diff; if !(resp_o.is_Some() && resp_o.get_Some_0().is_KResponse() && resp_o.get_Some_0().get_KResponse_0().is_CreateResponse() @@ -138,7 +138,7 @@ pub open spec fn reconcile_core( (error_state(state), None) } else if diff == 0 { let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::Done, + reconcile_step: VReplicaSetReconcileStep::Done, ..state }; (state_prime, None) @@ -149,13 +149,13 @@ pub open spec fn reconcile_core( obj: pod.marshal(), }); let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::AfterCreatePod((diff - 1) as nat), + reconcile_step: VReplicaSetReconcileStep::AfterCreatePod((diff - 1) as usize), ..state }; (state_prime, Some(RequestView::KRequest(req))) } }, - VReplicaSetReconcileStepView::AfterDeletePod(diff) => { + VReplicaSetReconcileStep::AfterDeletePod(diff) => { let diff = *diff; if !(resp_o.is_Some() && resp_o.get_Some_0().is_KResponse() && resp_o.get_Some_0().get_KResponse_0().is_DeleteResponse() @@ -163,7 +163,7 @@ pub open spec fn reconcile_core( (error_state(state), None) } else if diff == 0 { let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::Done, + reconcile_step: VReplicaSetReconcileStep::Done, ..state }; (state_prime, None) @@ -185,7 +185,7 @@ pub open spec fn reconcile_core( } }); let state_prime = VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::AfterDeletePod((diff - 1) as nat), + reconcile_step: VReplicaSetReconcileStep::AfterDeletePod((diff - 1) as usize), ..state }; (state_prime, Some(RequestView::KRequest(req))) @@ -202,7 +202,7 @@ pub open spec fn reconcile_core( pub open spec fn error_state(state: VReplicaSetReconcileState) -> (state_prime: VReplicaSetReconcileState) { VReplicaSetReconcileState { - reconcile_step: VReplicaSetReconcileStepView::Error, + reconcile_step: VReplicaSetReconcileStep::Error, ..state } } diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs index f9f2a23a9..6e7560404 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/mod.rs @@ -1,13 +1,8 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: MIT -pub mod owner_ref; pub mod predicate; pub mod proof; -pub mod unchangeable; -pub mod validation; -pub use owner_ref::*; pub use predicate::*; pub use proof::*; -pub use unchangeable::*; -pub use validation::*; + diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs deleted file mode 100644 index 0fb5cd2b8..000000000 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/owner_ref.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use crate::external_api::spec::*; -use crate::kubernetes_api_objects::spec::{ - api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, - stateful_set::*, -}; -use crate::kubernetes_cluster::spec::{ - builtin_controllers::types::BuiltinControllerChoice, - cluster::*, - cluster_state_machine::Step, - controller::types::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::temporal_logic::{defs::*, rules::*}; -use crate::v_replica_set_controller::{ - model::reconciler::*, - proof::{ - helper_invariants::{predicate::*, proof::*}, - predicate::*, - }, - trusted::{spec_types::*, step::*}, -}; -use vstd::prelude::*; - -verus! { - -} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs deleted file mode 100644 index 5e2c8c356..000000000 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/unchangeable.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; -use crate::kubernetes_api_objects::spec::{ - api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, -}; -use crate::kubernetes_cluster::spec::{ - cluster::*, - cluster_state_machine::Step, - controller::types::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::reconciler::spec::{reconciler::*, resource_builder::*}; -use crate::temporal_logic::{defs::*, rules::*}; -use crate::vstd_ext::{multiset_lib, seq_lib::*, string_view::*}; -use crate::v_replica_set_controller::{ - model::{reconciler::*}, - proof::{ - helper_invariants::{owner_ref::*, predicate::*, proof::*, validation::*}, - predicate::*, - }, - trusted::{ - spec_types::*, step::*, - }, -}; -use vstd::{multiset::*, prelude::*, seq_lib::*, string::*}; - -verus! { - -} diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs deleted file mode 100644 index 624d93cc8..000000000 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/validation.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; -use crate::kubernetes_api_objects::spec::{ - api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, - stateful_set::*, -}; -use crate::kubernetes_cluster::spec::{ - cluster::*, - cluster_state_machine::Step, - controller::types::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::reconciler::spec::{reconciler::*, resource_builder::*}; -use crate::temporal_logic::{defs::*, rules::*}; -use crate::vstd_ext::{multiset_lib, seq_lib, string_view::*}; -use crate::v_replica_set_controller::{ - model::{reconciler::*}, - proof::{ - helper_invariants::{owner_ref::*, predicate::*, proof::*}, - predicate::*, - }, - trusted::{spec_types::*, step::*}, -}; -use vstd::{multiset::*, prelude::*, string::*}; - -verus! { - -} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs index 9013484db..5af1a566f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs @@ -1,7 +1,6 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: MIT pub mod proof; -// pub mod resource_match; +pub mod resource_match; pub mod spec; -// pub mod stateful_set_match; -// pub mod terminate; +pub mod terminate; diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs index 267318427..0a97d5ba6 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs @@ -17,19 +17,12 @@ use crate::vstd_ext::{map_lib::*, string_view::*}; use crate::v_replica_set_controller::{ model::{reconciler::*}, proof::{ - //helper_invariants, + helper_invariants, liveness::{ - //resource_match::*, spec::*, - // stateful_set_match::{ - // lemma_from_after_get_stateful_set_step_to_stateful_set_matches, - // lemma_stateful_set_is_stable, - // }, - // terminate, - //zookeeper_api::lemma_from_after_exists_stateful_set_step_to_after_get_stateful_set_step, + terminate, }, - // predicate::*, - // resource::*, + predicate::*, }, trusted::{liveness_theorem::*, spec_types::*, step::*}, }; @@ -81,209 +74,25 @@ proof fn spec_before_phase_n_entails_true_leads_to_current_state_matches(i: nat, ensures spec_before_phase_n(i, vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))), { assume(false); - // reveal_with_fuel(spec_before_phase_n, 8); - // temp_pred_equality(spec_before_phase_n(i + 1, zookeeper), spec_before_phase_n(i, zookeeper).and(invariants_since_phase_n(i, zookeeper))); - // spec_of_previous_phases_entails_eventually_new_invariants(i, zookeeper); - // unpack_conditions_from_spec(spec_before_phase_n(i, zookeeper), invariants_since_phase_n(i, zookeeper), true_pred(), always(lift_state(current_state_matches::(zookeeper)))); - // temp_pred_equality(true_pred().and(invariants_since_phase_n(i, zookeeper)), invariants_since_phase_n(i, zookeeper)); - // leads_to_trans_temp(spec_before_phase_n(i, zookeeper), true_pred(), invariants_since_phase_n(i, zookeeper), always(lift_state(current_state_matches::(zookeeper)))); } proof fn lemma_true_leads_to_always_current_state_matches(vrs: VReplicaSetView) ensures assumption_and_invariants_of_all_phases(vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))), { - assume(false); - // let spec = assumption_and_invariants_of_all_phases(vrs); - - // assert forall |action: ActionKind, sub_resource: SubResource| #![auto] spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource)))))) by { - // always_tla_forall_apply(spec, |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))), (action, sub_resource)); - // } - - // // The use of termination property ensures spec |= true ~> reconcile_idle. - // terminate::reconcile_eventually_terminates(spec, zookeeper); - // // Then we can continue to show that spec |= reconcile_idle ~> []current_state_matches(zookeeper). - - // // The following two lemmas show that spec |= reconcile_idle ~> init /\ no_pending_req. - // lemma_from_reconcile_idle_to_scheduled(spec, zookeeper); - // lemma_from_scheduled_to_init_step(spec, zookeeper); - - // // After applying this lemma, we get spec |= init /\ no_pending_req ~> create_headless_service /\ pending_req. - // lemma_from_init_step_to_after_create_headless_service_step(spec, zookeeper); - - // // always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper)), SubResource::ConfigMap); - - // // We first show that the reconciler can go to at_after_get_resource_step(next_resource) from at_after_get_resource_step(sub_resource) - // // where sub_resource cannot be StatefulSet because it's the last resource to be processed and doesn't have its next_resource. - // // Through this, we can string all the resources together in sequence. This also means that the system can go to any - // // at_after_get_resource_step(sub_resource) from an arbitrary state. - // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet && sub_resource != SubResource::ConfigMap implies - // spec.entails( - // lift_state(#[trigger] pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) - // .leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) - // ) by { - // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); - // lemma_from_after_get_resource_step_to_resource_matches(spec, zookeeper, sub_resource); - // } - // // Thanks to the recursive construction of macro. - // leads_to_trans_n!( - // spec, true_pred(), lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }), - // lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) && s.scheduled_reconciles().contains_key(zookeeper.object_ref())}), - // lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)), - // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper)), - // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::ClientService, zookeeper)), - // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::AdminServerService, zookeeper)), - // lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::ConfigMap, zookeeper)) - // ); + let spec = assumption_and_invariants_of_all_phases(vrs); + + assert forall |k : usize| #![auto] spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k)))))) by { + always_tla_forall_apply(spec, |k: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k)))), k); + } + assert forall |k : usize| #![auto] spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k)))))) by { + always_tla_forall_apply(spec, |k: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k)))), k); + } + + // The use of termination property ensures spec |= true ~> reconcile_idle. + terminate::reconcile_eventually_terminates(spec, vrs); + // Then we can continue to show that spec |= reconcile_idle ~> []current_state_matches(vrs). - // // Since we already have true ~> at_after_get_resource_step(sub_resource), and we can get at_after_get_resource_step(sub_resource) - // // ~> sub_resource_state_matches(sub_resource, zookeeper) by applying lemma lemma_from_after_get_resource_step_to_resource_matches, - // // we now have true ~> sub_resource_state_matches(sub_resource, zookeeper). - // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet implies - // spec.entails( - // true_pred().leads_to(lift_state(#[trigger] sub_resource_state_matches(sub_resource, zookeeper))) - // ) by { - // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); - // lemma_from_after_get_resource_step_to_resource_matches(spec, zookeeper, sub_resource); - // leads_to_trans_temp( - // spec, true_pred(), lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)), - // lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - // ); - // } - - // // Now we further prove stability: given true ~> sub_resource_state_matches(sub_resource, zookeeper) - // // we prove true ~> []sub_resource_state_matches(sub_resource, zookeeper) - // assert forall |sub_resource: SubResource| sub_resource != SubResource::StatefulSet implies - // spec.entails( - // true_pred().leads_to(always(lift_state(#[trigger] sub_resource_state_matches(sub_resource, zookeeper)))) - // ) by { - // always_tla_forall_apply_for_sub_resource(spec, sub_resource, zookeeper); - // lemma_resource_object_is_stable(spec, sub_resource, zookeeper, true_pred()); - // } + assume(false); } -// proof fn lemma_from_reconcile_idle_to_scheduled(spec: TempPred, vrs: VReplicaSetView) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::schedule_controller_reconcile().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), -// ensures -// spec.entails(lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) -// .leads_to(lift_state(|s: ZKCluster| { -// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) -// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) -// }))), -// { -// let pre = |s: ZKCluster| { -// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) -// &&& !s.scheduled_reconciles().contains_key(zookeeper.object_ref()) -// }; -// let post = |s: ZKCluster| { -// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) -// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) -// }; -// let input = zookeeper.object_ref(); -// ZKCluster::lemma_pre_leads_to_post_by_schedule_controller_reconcile_borrow_from_spec(spec, input, ZKCluster::next(), ZKCluster::desired_state_is(zookeeper), pre, post); -// valid_implies_implies_leads_to(spec, lift_state(post), lift_state(post)); -// or_leads_to_combine_temp(spec, lift_state(pre), lift_state(post), lift_state(post)); -// temp_pred_equality(lift_state(pre).or(lift_state(post)), lift_state(|s: ZKCluster| {!s.ongoing_reconciles().contains_key(zookeeper.object_ref())})); -// } - -// proof fn lemma_from_scheduled_to_init_step(spec: TempPred, vrs: VReplicaSetView) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))), -// spec.entails(always(lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)))), -// ensures -// spec.entails(lift_state(|s: ZKCluster| { -// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) -// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) -// }).leads_to(lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)))), -// { -// let pre = |s: ZKCluster| { -// &&& !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) -// &&& s.scheduled_reconciles().contains_key(zookeeper.object_ref()) -// }; -// let post = no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init); -// let input = (None, Some(zookeeper.object_ref())); -// let stronger_next = |s, s_prime| { -// &&& ZKCluster::next()(s, s_prime) -// &&& ZKCluster::crash_disabled()(s) -// &&& ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()(s) -// &&& ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)(s) -// }; -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), -// lift_action(ZKCluster::next()), -// lift_state(ZKCluster::crash_disabled()), -// lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), -// lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)) -// ); -// ZKCluster::lemma_pre_leads_to_post_by_controller(spec, input, stronger_next, ZKCluster::run_scheduled_reconcile(), pre, post); -// } - -// proof fn lemma_from_init_step_to_after_create_headless_service_step(spec: TempPred, vrs: VReplicaSetView) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// ensures -// spec.entails(lift_state(no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init)).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper)))), -// { -// let pre = no_pending_req_at_zookeeper_step_with_zookeeper(zookeeper, ZookeeperReconcileStep::Init); -// let post = pending_req_in_flight_at_after_get_resource_step(SubResource::HeadlessService, zookeeper); -// let input = (None, Some(zookeeper.object_ref())); -// let stronger_next = |s, s_prime: ZKCluster| { -// &&& ZKCluster::next()(s, s_prime) -// &&& ZKCluster::crash_disabled()(s) -// }; -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), lift_action(ZKCluster::next()), lift_state(ZKCluster::crash_disabled()) -// ); -// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { -// let step = choose |step| ZKCluster::next_step(s, s_prime, step); -// match step { -// Step::ControllerStep(input) => { -// if input.1.get_Some_0() != zookeeper.object_ref() { -// assert(pre(s_prime)); -// } else { -// assert(post(s_prime)); -// } -// }, -// _ => { -// assert(pre(s_prime)); -// } -// } -// } -// ZKCluster::lemma_pre_leads_to_post_by_controller(spec, input, stronger_next, ZKCluster::continue_reconcile(), pre, post); -// } - -// proof fn always_tla_forall_apply_for_sub_resource(spec: TempPred, sub_resource: SubResource, vrs: VReplicaSetView) -// requires -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(res, zookeeper))))), -// spec.entails(always(tla_forall(|res: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(res, zookeeper))))), -// ensures -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), -// { -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(res, zookeeper)), sub_resource); -// always_tla_forall_apply(spec, |res: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(res, zookeeper)), sub_resource); -// } - } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index fd9ae2625..00d22e4a5 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -14,976 +14,108 @@ use crate::kubernetes_cluster::spec::{ }; use crate::temporal_logic::{defs::*, rules::*}; use crate::vstd_ext::{map_lib::*, string_view::*}; -use crate::zookeeper_controller::{ - model::{reconciler::*, resource::*}, - proof::{helper_invariants, predicate::*, resource::*}, - trusted::{spec_types::*, step::*}, +use crate::v_replica_set_controller::{ + model::{reconciler::*}, + proof::{helper_invariants, predicate::*}, + trusted::{spec_types::*, step::*, liveness_theorem::*}, }; use vstd::{prelude::*, string::*}; verus! { -pub proof fn lemma_from_after_get_resource_step_to_resource_matches( - spec: TempPred, zookeeper: ZookeeperClusterView, sub_resource: SubResource +#[verifier(external_body)] +pub proof fn lemma_from_after_list_pods_step_to_after_scale_pod_step_or_done( + spec: TempPred, vrs: VReplicaSetView, diff: nat, ) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), - spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), ensures - spec.entails( - lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) - .leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) - ), - sub_resource != SubResource::ConfigMap ==> spec.entails( - lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) - .leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) + // Case 1: Number of matching pods is less than the replica count. + diff > 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), diff) + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (diff - 1) as nat)(s) + })) ), - sub_resource == SubResource::ConfigMap ==> spec.entails( - lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) - .leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + // Case 2: Number of matching pods is greater than the replica count. + diff > 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_more_pods_is(vrs, s.resources(), diff) + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& num_more_pods_is(vrs, s.resources(), (diff - 1) as nat) + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) + })) ), -{ - lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches(spec, sub_resource, zookeeper); - lemma_from_after_get_resource_step_and_key_exists_to_resource_matches(spec, sub_resource, zookeeper); - let key_not_exists = lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }); - let key_exists = lift_state(|s: ZKCluster| { - &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }); - or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); - temp_pred_equality( - key_not_exists.or(key_exists), lift_state(pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)) - ); - - let next_state = if sub_resource != SubResource::ConfigMap { - pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) - } else { - pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type - }; - or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(next_state)); -} - -pub proof fn lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches( - spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView -) - requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), - ensures + // Case 3: Number of matching pods equals the replica count. spec.entails( - lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) - ), - sub_resource != SubResource::ConfigMap && sub_resource != SubResource::StatefulSet ==> spec.entails( - lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) - ), - sub_resource == SubResource::ConfigMap ==> spec.entails( - lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + lift_state(|s: VRSCluster| { + &&& num_more_pods_is(vrs, s.resources(), 0) + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& resource_state_matches(vrs, s.resources()) + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) + })) ), { - let pre = lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }); - let post = lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) - }); - let pre_and_req_in_flight = |req_msg| lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) - }); - let pre_and_exists_resp_in_flight = lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) - }); - let pre_and_resp_in_flight = |resp_msg| lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }); - let post_and_req_in_flight = |req_msg| lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) - }); - let match_and_ok_resp = lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); - let next_state = if sub_resource != SubResource::ConfigMap { - pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) - } else { - pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type - }; - - assert_by(spec.entails(pre.leads_to(match_and_ok_resp)), { - assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(pre_and_exists_resp_in_flight)) by { - lemma_from_key_not_exists_to_receives_not_found_resp_at_after_get_resource_step(spec, sub_resource, zookeeper, req_msg); - } - leads_to_exists_intro(spec, pre_and_req_in_flight, pre_and_exists_resp_in_flight); - assert_by(tla_exists(pre_and_req_in_flight) == pre, { - assert forall |ex| #[trigger] pre.satisfied_by(ex) implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(pre_and_req_in_flight), pre); - }); - - assert forall |resp_msg| spec.entails(#[trigger] pre_and_resp_in_flight(resp_msg).leads_to(post)) by { - lemma_from_after_get_resource_step_to_after_create_resource_step(spec, sub_resource, zookeeper, resp_msg); - } - leads_to_exists_intro(spec, pre_and_resp_in_flight, post); - assert_by(tla_exists(pre_and_resp_in_flight) == pre_and_exists_resp_in_flight, { - assert forall |ex| #[trigger] pre_and_exists_resp_in_flight.satisfied_by(ex) implies tla_exists(pre_and_resp_in_flight).satisfied_by(ex) by { - let resp_msg = choose |resp_msg| { - &&& #[trigger] ex.head().in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }; - assert(pre_and_resp_in_flight(resp_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(pre_and_resp_in_flight), pre_and_exists_resp_in_flight); - }); - - assert forall |req_msg| spec.entails(#[trigger] post_and_req_in_flight(req_msg).leads_to(match_and_ok_resp)) by { - lemma_resource_state_matches_at_after_create_resource_step(spec, sub_resource, zookeeper, req_msg); - } - leads_to_exists_intro(spec, post_and_req_in_flight, match_and_ok_resp); - assert_by(tla_exists(post_and_req_in_flight) == post, { - assert forall |ex| #[trigger] post.satisfied_by(ex) implies tla_exists(post_and_req_in_flight).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - assert(post_and_req_in_flight(req_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(post_and_req_in_flight), post); - }); - leads_to_trans_n!(spec, pre, pre_and_exists_resp_in_flight, post, match_and_ok_resp); - }); - - assert_by(spec.entails(pre.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), { - valid_implies_implies_leads_to(spec, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); - leads_to_trans_n!(spec, pre, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); - }); - - // We already have the desired state. - // Now prove the system can successfully enter the next state. - if sub_resource != SubResource::StatefulSet { - assert_by(spec.entails(pre.leads_to(lift_state(next_state))), { - let known_ok_resp = |resp_msg: ZKMessage| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_create_resource_step(sub_resource, zookeeper, resp_msg)); - assert forall |resp_msg| spec.entails(#[trigger] known_ok_resp(resp_msg).leads_to(lift_state(next_state))) by { - let pre = resp_msg_is_the_in_flight_ok_resp_at_after_create_resource_step(sub_resource, zookeeper, resp_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) - }; - - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())) - ); - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || next_state(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ControllerStep(input) => { - if input.1.is_Some() && input.1.get_Some_0() == zookeeper.object_ref() { - assert(next_state(s_prime)); - } else { - assert(pre(s_prime)); - } - } - _ => { - assert(pre(s_prime)); - } - } - } - ZKCluster::lemma_pre_leads_to_post_by_controller( - spec, (Some(resp_msg), Some(zookeeper.object_ref())), stronger_next, ZKCluster::continue_reconcile(), pre, next_state - ); - } - leads_to_exists_intro(spec, known_ok_resp, lift_state(next_state)); - let exists_ok_resp = lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)); - assert_by(tla_exists(known_ok_resp) == exists_ok_resp, { - assert forall |ex| #[trigger] exists_ok_resp.satisfied_by(ex) implies tla_exists(known_ok_resp).satisfied_by(ex) by { - let resp_msg = choose |resp_msg| { - &&& #[trigger] ex.head().in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) - &&& resp_msg.content.get_create_response().res.is_Ok() - &&& state_after_create(sub_resource, zookeeper, resp_msg.content.get_create_response().res.get_Ok_0(), ex.head().ongoing_reconciles()[zookeeper.object_ref()].local_state).is_Ok() - }; - assert(known_ok_resp(resp_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(known_ok_resp), exists_ok_resp); - }); - valid_implies_implies_leads_to(spec, match_and_ok_resp, exists_ok_resp); - leads_to_trans_n!(spec, pre, match_and_ok_resp, exists_ok_resp, lift_state(next_state)); - }); - } } -proof fn lemma_from_after_get_resource_step_and_key_exists_to_resource_matches( - spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView +#[verifier(external_body)] +pub proof fn lemma_from_after_create_pod_step_to_after_create_pod_step_or_done( + spec: TempPred, vrs: VReplicaSetView, diff: nat, ) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), ensures - spec.entails( - lift_state(|s: ZKCluster| { - &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper))) - ), - sub_resource != SubResource::ConfigMap ==> spec.entails( - lift_state(|s: ZKCluster| { - &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper))) - ), - sub_resource == SubResource::ConfigMap ==> spec.entails( - lift_state(|s: ZKCluster| { - &&& s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }).leads_to(lift_state(pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper))) + // Case 1: Number of matching pods is less than the replica count. + diff > 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), diff) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, diff)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (diff - 1) as nat)(s) + })) ), -{ - let resource_key = get_request(sub_resource, zookeeper).key; - let pre = lift_state(|s: ZKCluster| { - &&& s.resources().contains_key(resource_key) - &&& pending_req_in_flight_at_after_get_resource_step(sub_resource, zookeeper)(s) - }); - let post = pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper); - let match_and_ok_resp = lift_state(sub_resource_state_matches(sub_resource, zookeeper)).and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); - let next_state = if sub_resource != SubResource::ConfigMap { - pending_req_in_flight_at_after_get_resource_step(next_resource_after(sub_resource).get_AfterKRequestStep_1(), zookeeper) - } else { - pending_req_in_flight_at_after_exists_stateful_set_step(zookeeper) // ConfigMap is a bit different since its next step is not a SubResource type - }; - assert_by(spec.entails(pre.leads_to(match_and_ok_resp)), { - let pre_and_req_in_flight = |req_msg| lift_state(req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg)); - assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)))) - by { - lemma_from_key_exists_to_receives_ok_resp_at_after_get_resource_step(spec, sub_resource, zookeeper, req_msg); - } - leads_to_exists_intro(spec, pre_and_req_in_flight, lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); - assert_by(tla_exists(pre_and_req_in_flight) == pre, { - assert forall |ex| #[trigger] pre.satisfied_by(ex) - implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(pre_and_req_in_flight), pre); - }); - let pre_and_resp_in_flight = |resp_msg| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)); - assert forall |resp_msg| spec.entails(#[trigger] pre_and_resp_in_flight(resp_msg).leads_to(lift_state(post))) - by { - lemma_from_after_get_resource_step_to_after_update_resource_step(spec, sub_resource, zookeeper, resp_msg); - } - leads_to_exists_intro(spec, pre_and_resp_in_flight, lift_state(post)); - assert_by(tla_exists(pre_and_resp_in_flight) == lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)), { - assert forall |ex| #[trigger] lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)).satisfied_by(ex) - implies tla_exists(pre_and_resp_in_flight).satisfied_by(ex) by { - let resp_msg = choose |resp_msg| { - &&& #[trigger] ex.head().in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) - &&& resp_msg.content.get_get_response().res.is_Ok() - &&& resp_msg.content.get_get_response().res.get_Ok_0() == ex.head().resources()[resource_key] - }; - assert(pre_and_resp_in_flight(resp_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(pre_and_resp_in_flight), lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))); - }); - let pre_and_req_in_flight = |req_msg| lift_state(req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg)); - assert forall |req_msg| spec.entails(#[trigger] pre_and_req_in_flight(req_msg).leads_to(match_and_ok_resp)) by { - lemma_resource_state_matches_at_after_update_resource_step(spec, sub_resource, zookeeper, req_msg); - } - leads_to_exists_intro(spec, pre_and_req_in_flight, match_and_ok_resp); - assert_by(tla_exists(pre_and_req_in_flight) == lift_state(post), { - assert forall |ex| #[trigger] lift_state(post).satisfied_by(ex) - implies tla_exists(pre_and_req_in_flight).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - assert(pre_and_req_in_flight(req_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(pre_and_req_in_flight), lift_state(post)); - }); - leads_to_trans_n!( - spec, pre, lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)), - lift_state(post), match_and_ok_resp - ); - }); - - assert_by(spec.entails(pre.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), { - valid_implies_implies_leads_to(spec, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); - leads_to_trans_n!(spec, pre, match_and_ok_resp, lift_state(sub_resource_state_matches(sub_resource, zookeeper))); - }); - - // We already have the desired state. - // Now prove the system can successfully enter the next state. - assert_by(spec.entails(pre.leads_to(lift_state(next_state))), { - let known_ok_resp = |resp_msg: ZKMessage| lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_update_resource_step(sub_resource, zookeeper, resp_msg)); - assert forall |resp_msg| spec.entails(#[trigger] known_ok_resp(resp_msg).leads_to(lift_state(next_state))) by { - let pre = resp_msg_is_the_in_flight_ok_resp_at_after_update_resource_step(sub_resource, zookeeper, resp_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())) - ); - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || next_state(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ControllerStep(input) => { - if input.1.is_Some() && input.1.get_Some_0() == zookeeper.object_ref() { - assert(next_state(s_prime)); - } else { - assert(pre(s_prime)); - } - } - _ => { - assert(pre(s_prime)); - } - } - } - ZKCluster::lemma_pre_leads_to_post_by_controller( - spec, (Some(resp_msg), Some(zookeeper.object_ref())), stronger_next, ZKCluster::continue_reconcile(), pre, next_state - ); - } - leads_to_exists_intro(spec, known_ok_resp, lift_state(next_state)); - let exists_ok_resp = lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)); - assert_by(tla_exists(known_ok_resp) == exists_ok_resp, { - assert forall |ex| #[trigger] exists_ok_resp.satisfied_by(ex) implies tla_exists(known_ok_resp).satisfied_by(ex) by { - let resp_msg = choose |resp_msg| { - &&& #[trigger] ex.head().in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) - &&& resp_msg.content.get_update_response().res.is_Ok() - &&& state_after_update(sub_resource, zookeeper, resp_msg.content.get_update_response().res.get_Ok_0(), ex.head().ongoing_reconciles()[zookeeper.object_ref()].local_state).is_Ok() - }; - assert(known_ok_resp(resp_msg).satisfied_by(ex)); - } - temp_pred_equality(tla_exists(known_ok_resp), exists_ok_resp); - }); - valid_implies_implies_leads_to(spec, match_and_ok_resp, exists_ok_resp); - leads_to_trans_n!(spec, pre, match_and_ok_resp, exists_ok_resp, lift_state(next_state)); - }); -} - -proof fn lemma_from_key_not_exists_to_receives_not_found_resp_at_after_get_resource_step( - spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage -) - requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), - ensures - spec.entails( - lift_state( - |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) - } - ).leads_to(lift_state( - |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) - } - )) + // Case 2: No more pods to create. + diff == 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), 0) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, 0)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& resource_state_matches(vrs, s.resources()) + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) + })) ), { - let pre = |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step(sub_resource, zookeeper, req_msg)(s) - }; - let post = |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& at_after_get_resource_step_and_exists_not_found_resp_in_flight(sub_resource, zookeeper)(s) - }; - let input = Some(req_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)) - ); - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - assert(!resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0())); - if input.get_Some_0() == req_msg { - let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; - assert({ - &&& s_prime.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }); - } - }, - _ => {} - } - } - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) - implies post(s_prime) by { - let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; - assert({ - &&& s_prime.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }); - } - - ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( - spec, input, stronger_next, ZKCluster::handle_request(), pre, post - ); } -proof fn lemma_from_after_get_resource_step_to_after_create_resource_step( - spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage +#[verifier(external_body)] +pub proof fn lemma_from_after_delete_pod_step_to_after_delete_pod_step_or_done( + spec: TempPred, vrs: VReplicaSetView, diff: nat, ) - requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), ensures - spec.entails( - lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }).leads_to(lift_state(|s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) + // Case 1: Number of matching pods is less than the replica count. + diff > 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_more_pods_is(vrs, s.resources(), diff) + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, diff)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) })) ), -{ - let pre = |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& resp_msg_is_the_in_flight_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)(s) - &&& resp_msg.content.get_get_response().res.is_Err() - &&& resp_msg.content.get_get_response().res.get_Err_0().is_ObjectNotFound() - }; - let post = |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& pending_req_in_flight_at_after_create_resource_step(sub_resource, zookeeper)(s) - }; - let input = (Some(resp_msg), Some(zookeeper.object_ref())); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) - &&& ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) - &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) - &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) - }; - - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), - lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), - lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), - lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)), - lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)) - ); - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - assert(!resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0())); - }, - _ => {} - } - } - - ZKCluster::lemma_pre_leads_to_post_by_controller( - spec, input, stronger_next, ZKCluster::continue_reconcile(), pre, post - ); -} - -proof fn lemma_resource_state_matches_at_after_create_resource_step( - spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage -) - requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))), - ensures - spec.entails( - lift_state( - |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) - } - ).leads_to( - lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) - ) + // Case 2: No more pods to create. + diff == 0 ==> spec.entails( + lift_state(|s: VRSCluster| { + &&& num_more_pods_is(vrs, s.resources(), 0) + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, 0)(s) + }).leads_to(lift_state(|s: VRSCluster| { + &&& resource_state_matches(vrs, s.resources()) + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) + })) ), { - let pre = |s: ZKCluster| { - &&& !s.resources().contains_key(get_request(sub_resource, zookeeper).key) - &&& req_msg_is_the_in_flight_pending_req_at_after_create_resource_step(sub_resource, zookeeper, req_msg)(s) - }; - let input = Some(req_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) - &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) - &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) - &&& helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), - lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), - lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), - lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)) - ); - - let post = |s: ZKCluster| { - &&& sub_resource_state_matches(sub_resource, zookeeper)(s) - &&& at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)(s) - }; - - assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { - let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - let resp = ZKCluster::handle_create_request_msg(pending_msg, s.kubernetes_api_state).1; - assert(s_prime.in_flight().contains(resp)); - match sub_resource { - SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), - SubResource::ClientService => ServiceView::marshal_preserves_integrity(), - SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), - SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), - SubResource::StatefulSet => StatefulSetView::marshal_preserves_integrity(), - } - } - - assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - if resource_create_request_msg(get_request(sub_resource, zookeeper).key)(input.get_Some_0()) {} else {} - }, - _ => {}, - } - } - - ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( - spec, input, stronger_next, ZKCluster::handle_request(), pre, post - ); - temp_pred_equality( - lift_state(post), - lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - .and(lift_state(at_after_create_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) - ); -} - -proof fn lemma_from_key_exists_to_receives_ok_resp_at_after_get_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - ensures - spec.entails(lift_state(req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg)) - .leads_to(lift_state(at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)))), -{ - let pre = req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(sub_resource, zookeeper, req_msg); - let post = at_after_get_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper); - let resource_key = get_request(sub_resource, zookeeper).key; - let input = Some(req_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) - &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) - &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), - lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), - lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)) - ); - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - let req = input.get_Some_0(); - assert(!resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req)); - assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); - assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); - if input.get_Some_0() == req_msg { - let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; - assert({ - &&& s_prime.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) - &&& resp_msg.content.get_get_response().res.is_Ok() - &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] - }); - assert(post(s_prime)); - } - }, - _ => {} - } - } - - assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) - implies post(s_prime) by { - let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; - assert({ - &&& s_prime.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) - &&& resp_msg.content.get_get_response().res.is_Ok() - &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] - }); - } - - ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( - spec, input, stronger_next, ZKCluster::handle_request(), pre, post - ); -} - -proof fn lemma_resource_state_matches_at_after_update_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), - ensures - spec.entails(lift_state(req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg)) - .leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - .and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))))), -{ - let pre = req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(sub_resource, zookeeper, req_msg); - let resource_key = get_request(sub_resource, zookeeper).key; - let input = Some(req_msg); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) - &&& ZKCluster::desired_state_is(zookeeper)(s) - &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) - &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) - &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) - &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) - &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)(s) - &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) - &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), - lift_state(ZKCluster::desired_state_is(zookeeper)), - lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), - lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), - lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), - lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), - lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)), - lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)), - lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)) - ); - - let post = |s: ZKCluster| { - &&& sub_resource_state_matches(sub_resource, zookeeper)(s) - &&& at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper)(s) - }; - - assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { - let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); - let resp = ZKCluster::handle_update_request_msg(pending_msg, s.kubernetes_api_state).1; - assert(s_prime.in_flight().contains(resp)); - match sub_resource { - SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), - SubResource::ClientService => ServiceView::marshal_preserves_integrity(), - SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), - SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), - _ => {} - } - } - - assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - assert(!resource_delete_request_msg(resource_key)(input.get_Some_0())); - assert(!resource_update_status_request_msg(resource_key)(input.get_Some_0())); - if resource_update_request_msg(resource_key)(input.get_Some_0()) {} else {} - }, - _ => {}, - } - } - - ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api(spec, input, stronger_next, ZKCluster::handle_request(), pre, post); - temp_pred_equality( - lift_state(post), - lift_state(sub_resource_state_matches(sub_resource, zookeeper)) - .and(lift_state(at_after_update_resource_step_and_exists_ok_resp_in_flight(sub_resource, zookeeper))) - ); } -proof fn lemma_from_after_get_resource_step_to_after_update_resource_step(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), - ensures spec.entails(lift_state(resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg)).leads_to(lift_state(pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper)))), -{ - let pre = resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(sub_resource, zookeeper, resp_msg); - let post = pending_req_in_flight_at_after_update_resource_step(sub_resource, zookeeper); - let input = (Some(resp_msg), Some(zookeeper.object_ref())); - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& ZKCluster::crash_disabled()(s) - &&& ZKCluster::busy_disabled()(s) - &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) - &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) - &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) - &&& ZKCluster::desired_state_is(zookeeper)(s) - &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) - &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) - &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) - &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) - &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)(s) - }; - - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(ZKCluster::crash_disabled()), - lift_state(ZKCluster::busy_disabled()), - lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), - lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), - lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), - lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), - lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), - lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)), - lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)), - lift_state(ZKCluster::desired_state_is(zookeeper)) - ); - - assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - let req = input.get_Some_0(); - assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); - assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); - if resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req) {} else {} - }, - _ => {}, - } - } - - ZKCluster::lemma_pre_leads_to_post_by_controller( - spec, input, stronger_next, - ZKCluster::continue_reconcile(), pre, post - ); -} - -pub proof fn lemma_resource_object_is_stable(spec: TempPred, sub_resource: SubResource, zookeeper: ZookeeperClusterView, p: TempPred) - requires - sub_resource != SubResource::StatefulSet, - spec.entails(p.leads_to(lift_state(sub_resource_state_matches(sub_resource, zookeeper)))), - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)))), - spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)))), - ensures spec.entails(p.leads_to(always(lift_state(sub_resource_state_matches(sub_resource, zookeeper))))), -{ - let post = sub_resource_state_matches(sub_resource, zookeeper); - let resource_key = get_request(sub_resource, zookeeper).key; - let stronger_next = |s, s_prime: ZKCluster| { - &&& ZKCluster::next()(s, s_prime) - &&& helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)(s) - &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)(s) - &&& helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)(s) - &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)(s) - &&& helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)(s) - }; - combine_spec_entails_always_n!( - spec, lift_action(stronger_next), - lift_action(ZKCluster::next()), - lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)), - lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)), - lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(sub_resource, zookeeper)), - lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)), - lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)) - ); - - assert forall |s, s_prime: ZKCluster| post(s) && #[trigger] stronger_next(s, s_prime) implies post(s_prime) by { - let step = choose |step| ZKCluster::next_step(s, s_prime, step); - match step { - Step::ApiServerStep(input) => { - let req = input.get_Some_0(); - assert(!resource_delete_request_msg(get_request(sub_resource, zookeeper).key)(req)); - assert(!resource_update_status_request_msg(get_request(sub_resource, zookeeper).key)(req)); - if resource_update_request_msg(get_request(sub_resource, zookeeper).key)(req) {} else {} - }, - _ => {}, - } - match sub_resource { - SubResource::HeadlessService => ServiceView::marshal_preserves_integrity(), - SubResource::ClientService => ServiceView::marshal_preserves_integrity(), - SubResource::AdminServerService => ServiceView::marshal_preserves_integrity(), - SubResource::ConfigMap => ConfigMapView::marshal_preserves_integrity(), - _ => {} - } - } - - leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); -} } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index f13f3c2ae..562789af6 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -16,7 +16,7 @@ use crate::kubernetes_cluster::spec::{ use crate::temporal_logic::{defs::*, rules::*}; use crate::v_replica_set_controller::{ model::reconciler::*, - //proof::{helper_invariants, liveness::terminate, predicate::*, resource::*}, + proof::{helper_invariants, liveness::terminate, predicate::*}, trusted::{liveness_theorem::*, spec_types::*, step::*}, }; use vstd::prelude::*; @@ -69,59 +69,6 @@ pub open spec fn spec_before_phase_n(n: nat, vrs: VReplicaSetView) -> TempPred valid(stable(#[trigger] spec_before_phase_n(i, vrs))), { assume(false) - // reveal_with_fuel(spec_before_phase_n, 8); - // invariants_is_stable(zookeeper); - // always_p_is_stable(lift_state(desired_state_is(zookeeper))); - // invariants_since_phase_i_is_stable(zookeeper); - // invariants_since_phase_ii_is_stable(zookeeper); - // invariants_since_phase_iii_is_stable(zookeeper); - // invariants_since_phase_iv_is_stable(zookeeper); - // invariants_since_phase_v_is_stable(zookeeper); - // invariants_since_phase_vi_is_stable(zookeeper); - // invariants_since_phase_vii_is_stable(zookeeper); - // stable_and_n!( - // invariants(zookeeper), always(lift_state(desired_state_is(zookeeper))), - // invariants_since_phase_i(zookeeper), invariants_since_phase_ii(zookeeper), invariants_since_phase_iii(zookeeper), - // invariants_since_phase_iv(zookeeper), invariants_since_phase_v(zookeeper), invariants_since_phase_vi(zookeeper), - // invariants_since_phase_vii(zookeeper) - // ); } // Next and all the wf conditions. @@ -159,29 +90,6 @@ pub open spec fn next_with_wf() -> TempPred { .and(VRSCluster::disable_transient_failure().weak_fairness(())) } -// pub proof fn next_with_wf_is_stable() -// ensures valid(stable(next_with_wf())), -// { -// always_p_is_stable(lift_action(ZKCluster::next())); -// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::kubernetes_api_next()); -// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::external_api_next()); -// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::controller_next()); -// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::schedule_controller_reconcile()); -// ZKCluster::tla_forall_action_weak_fairness_is_stable(ZKCluster::builtin_controllers_next()); -// ZKCluster::action_weak_fairness_is_stable(ZKCluster::disable_crash()); -// ZKCluster::action_weak_fairness_is_stable(ZKCluster::disable_transient_failure()); -// stable_and_n!( -// always(lift_action(ZKCluster::next())), -// tla_forall(|input| ZKCluster::kubernetes_api_next().weak_fairness(input)), -// tla_forall(|input| ZKCluster::external_api_next().weak_fairness(input)), -// tla_forall(|input| ZKCluster::controller_next().weak_fairness(input)), -// tla_forall(|input| ZKCluster::schedule_controller_reconcile().weak_fairness(input)), -// tla_forall(|input| ZKCluster::builtin_controllers_next().weak_fairness(input)), -// ZKCluster::disable_crash().weak_fairness(()), -// ZKCluster::disable_transient_failure().weak_fairness(()) -// ); -// } - /// This predicate combines all the possible actions (next), weak fairness and invariants that hold throughout the execution. /// We name it invariants here because these predicates are never violated, thus they can all be seen as some kind of invariants. /// @@ -191,384 +99,18 @@ pub open spec fn invariants(vrs: VReplicaSetView) -> TempPred { next_with_wf().and(derived_invariants_since_beginning(vrs)) } -// pub proof fn invariants_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants(zookeeper))), -// { -// next_with_wf_is_stable(); -// derived_invariants_since_beginning_is_stable(zookeeper); -// stable_and_n!( -// next_with_wf(), -// derived_invariants_since_beginning(zookeeper) -// ); -// } - // The safety invariants that are required to prove liveness. pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> TempPred { - true_pred() - // always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id())) - // .and(always(lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()))) - // .and(always(lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())))) - // .and(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))) - // .and(always(lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()))) - // .and(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))) - // .and(always(lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))) - // .and(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) - // .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper))))) - // .and(always(lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))) - // .and(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))) - // .and(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1))))))) - // .and(always(tla_forall(|res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper))))) - // .and(always(lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)))) - // .and(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))) - // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) - // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) - // .and(always(lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())))) - // .and(always(tla_forall(|res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper))))) - // .and(always(tla_forall(|res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key))))) - // .and(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))) - // .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper))))) + always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id())) + .and(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterListPods))))) + .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) + .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) } -// pub proof fn derived_invariants_since_beginning_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(derived_invariants_since_beginning(zookeeper))), -// { -// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)); -// let a_to_p_2 = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); -// let a_to_p_3 = |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)); -// let a_to_p_4 = |res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper)); -// let a_to_p_5 = |res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key)); -// let a_to_p_6 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)); -// stable_and_always_n!( -// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), -// lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()), -// lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())), -// lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), -// lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()), -// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), -// lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), -// lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), -// tla_forall(a_to_p_1), -// lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))), -// lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))), -// tla_forall(a_to_p_2), -// tla_forall(a_to_p_3), -// lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)), -// lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), -// lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), -// lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), -// lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), -// tla_forall(a_to_p_4), -// tla_forall(a_to_p_5), -// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), -// tla_forall(a_to_p_6) -// ); -// } - -// /// The first notable phase comes when crash and k8s busy are always disabled and the object in schedule always has the same -// /// spec and uid as the cr we provide. -// /// -// /// Note that don't try to find any connections between those invariants -- they are put together because they don't have to -// /// wait for another of them to first be satisfied. -// pub open spec fn invariants_since_phase_i(zookeeper: ZookeeperClusterView) -> TempPred { -// always(lift_state(ZKCluster::crash_disabled())) -// .and(always(lift_state(ZKCluster::busy_disabled()))) -// .and(always(lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)))) -// } - -// pub proof fn invariants_since_phase_i_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_i(zookeeper))), -// { -// stable_and_always_n!( -// lift_state(ZKCluster::crash_disabled()), -// lift_state(ZKCluster::busy_disabled()), -// lift_state(ZKCluster::the_object_in_schedule_has_spec_and_uid_as(zookeeper)) -// ); -// } - -// /// For now, phase II only contains one invariant, which is the object in reconcile has the same spec and uid as zookeeper. -// /// -// /// It is alone because it relies on the invariant the_object_in_schedule_has_spec_and_uid_as (in phase I) and every invariant -// /// in phase III relies on it. -// pub open spec fn invariants_since_phase_ii(zookeeper: ZookeeperClusterView) -> TempPred { -// always(lift_state(ZKCluster::the_object_in_reconcile_has_spec_and_uid_as(zookeeper))) -// } - - -// pub proof fn invariants_since_phase_ii_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_ii(zookeeper))), -// { -// always_p_is_stable(lift_state(ZKCluster::the_object_in_reconcile_has_spec_and_uid_as(zookeeper))); -// } - -// pub open spec fn invariants_since_phase_iii(zookeeper: ZookeeperClusterView) -> TempPred { -// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)))) -// .and(always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr(sub_resource, zookeeper))))) -// .and(always(lift_state(helper_invariants::every_zk_set_data_request_implies_at_after_update_zk_node_step(zookeeper)))) -// .and(always(lift_state(helper_invariants::every_zk_create_node_request_implies_at_after_create_zk_node_step(zookeeper)))) -// } - -// pub proof fn invariants_since_phase_iii_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_iii(zookeeper))), -// { -// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(sub_resource, zookeeper)); -// let a_to_p_2 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_every_resource_update_request_only_has_owner_references_pointing_to_current_cr(sub_resource, zookeeper)); -// stable_and_always_n!(tla_forall(a_to_p_1), tla_forall(a_to_p_2), -// lift_state(helper_invariants::every_zk_set_data_request_implies_at_after_update_zk_node_step(zookeeper)), -// lift_state(helper_invariants::every_zk_create_node_request_implies_at_after_create_zk_node_step(zookeeper)) -// ); -// } - -// /// Invariants since this phase ensure that certain objects only have owner references that point to current cr. -// /// To have these invariants, we first need the invariant that evert create/update request make/change the object in the -// /// expected way. -// pub open spec fn invariants_since_phase_iv(zookeeper: ZookeeperClusterView) -> TempPred { -// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)))) -// } - -// pub proof fn invariants_since_phase_iv_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_iv(zookeeper))), -// { -// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(sub_resource, zookeeper)); -// always_p_is_stable(tla_forall(a_to_p_1)); -// } - -// /// Invariants since phase V rely on the invariants since phase IV. When the objects starts to always have owner reference -// /// pointing to current cr, it will never be recycled by the garbage collector. Plus, the reconciler itself never tries to -// /// delete this object, so we can have the invariants saying that no delete request messages will be in flight. -// pub open spec fn invariants_since_phase_v(zookeeper: ZookeeperClusterView) -> TempPred { -// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))) -// } - -// pub proof fn invariants_since_phase_v_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_v(zookeeper))), -// { -// always_p_is_stable(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(sub_resource, zookeeper)))); -// } - -// pub open spec fn invariants_since_phase_vi(zookeeper: ZookeeperClusterView) -> TempPred { -// always(tla_forall(|sub_resource: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)))) -// .and(always(lift_state(helper_invariants::object_in_response_at_after_create_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)))) -// .and(always(lift_state(helper_invariants::object_in_response_at_after_update_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)))) -// } - -// pub proof fn invariants_since_phase_vi_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_vi(zookeeper))), -// { -// let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(sub_resource, zookeeper)); -// stable_and_always_n!( -// tla_forall(a_to_p_1), -// lift_state(helper_invariants::object_in_response_at_after_create_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)), -// lift_state(helper_invariants::object_in_response_at_after_update_resource_step_is_same_as_etcd(SubResource::ConfigMap, zookeeper)) -// ); -// } - -// pub open spec fn invariants_since_phase_vii(zookeeper: ZookeeperClusterView) -> TempPred { -// always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper))) -// } - -// pub proof fn invariants_since_phase_vii_is_stable(zookeeper: ZookeeperClusterView) -// ensures valid(stable(invariants_since_phase_vii(zookeeper))), -// { -// always_p_is_stable(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper))); -// } - -// pub proof fn lemma_always_for_all_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures spec.entails(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1))))))), -// { -// // TODO (xudong): investigate the performance of this lemma -// // Somehow the reasoning inside the assert forall block below is very slow (takes more than 8 seconds!) -// // I suspect it is related to the precondition of lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state -// let a_to_p = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); -// assert_by(spec.entails(always(tla_forall(a_to_p))), { -// assert forall |step: (ActionKind, SubResource)| spec.entails(always(#[trigger] a_to_p(step))) by { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state( -// spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) -// ); -// } -// spec_entails_always_tla_forall(spec, a_to_p); -// }); -// } - -// pub proof fn lemma_always_for_after_exists_stateful_set_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures -// spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet)); -// } - -// pub proof fn lemma_always_for_after_exists_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures -// spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode)); -// } - -// pub proof fn lemma_always_for_after_create_zk_parent_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode)); -// } - -// pub proof fn lemma_always_for_after_create_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode)); -// } - -// pub proof fn lemma_always_for_after_update_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode)); -// } - -// pub proof fn lemma_always_for_after_update_status_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec: TempPred, zookeeper: ZookeeperClusterView) -// requires -// spec.entails(lift_state(ZKCluster::init())), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// ensures spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))), -// { -// ZKCluster::lemma_always_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus)); -// } - pub proof fn sm_spec_entails_all_invariants(vrs: VReplicaSetView) ensures cluster_spec().entails(derived_invariants_since_beginning(vrs)), { assume(false); - // let spec = cluster_spec(); - // // Adding two assertions to make the verification faster because all the lemmas below require the two preconditions. - // // And then the verifier doesn't have to infer it every time applying those lemmas. - // assert(spec.entails(lift_state(ZKCluster::init()))); - // assert(spec.entails(always(lift_action(ZKCluster::next())))); - // ZKCluster::lemma_always_every_in_flight_msg_has_unique_id(spec); - // ZKCluster::lemma_always_object_in_ok_get_response_has_smaller_rv_than_etcd(spec); - // ZKCluster::lemma_always_every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(spec, zookeeper.object_ref()); - // ZKCluster::lemma_always_pending_req_of_key_is_unique_with_unique_id(spec, zookeeper.object_ref()); - // ZKCluster::lemma_always_every_in_flight_msg_has_lower_id_than_allocator(spec); - // ZKCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - // ZKCluster::lemma_always_each_scheduled_object_has_consistent_key_and_valid_metadata(spec); - // ZKCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - // let a_to_p_1 = |sub_resource: SubResource| lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(sub_resource, zookeeper)); - // assert_by(spec.entails(always(tla_forall(a_to_p_1))), { - // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_1(sub_resource))) by { - // helper_invariants::lemma_always_resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(spec, sub_resource, zookeeper); - // } - // spec_entails_always_tla_forall(spec, a_to_p_1); - // }); - // ZKCluster::lemma_always_no_pending_req_msg_at_reconcile_state(spec, zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init)); - - // // Different from other a_to_p_x, we encapsulate a_to_p_2 inside the lemma below because we find its reasoning is - // // surprisingly slow in this context. Encapsulating the reasoning reduces the verification time of this function - // // from more than 40 seconds to 2 seconds. - // let a_to_p_2 = |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)))); - // lemma_always_for_all_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // // lemma_always_for_all_non_sub_resource_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_exists_stateful_set_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_exists_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_create_zk_parent_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_create_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_update_zk_node_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - // lemma_always_for_after_update_status_step_pending_req_in_flight_or_resp_in_flight_at_reconcile_state(spec, zookeeper); - - // let a_to_p_3 = |res: SubResource| lift_state(helper_invariants::no_update_status_request_msg_in_flight_of_except_stateful_set(res, zookeeper)); - // assert_by(spec.entails(always(tla_forall(a_to_p_3))), { - // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_3(sub_resource))) by { - // helper_invariants::lemma_always_no_update_status_request_msg_in_flight_of_except_stateful_set(spec, sub_resource, zookeeper); - // } - // spec_entails_always_tla_forall(spec, a_to_p_3); - // }); - // helper_invariants::lemma_always_no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(spec, zookeeper); - // helper_invariants::lemma_always_the_object_in_reconcile_satisfies_state_validation(spec, zookeeper.object_ref()); - // ZKCluster::lemma_always_key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); - // ZKCluster::lemma_always_key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); - // ZKCluster::lemma_always_key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(spec, zookeeper.object_ref()); - // let a_to_p_4 = |res: SubResource| lift_state(helper_invariants::response_at_after_get_resource_step_is_resource_get_response(res, zookeeper)); - // assert_by(spec.entails(always(tla_forall(a_to_p_4))), { - // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_4(sub_resource))) by { - // helper_invariants::lemma_always_response_at_after_get_resource_step_is_resource_get_response(spec, sub_resource, zookeeper); - // } - // spec_entails_always_tla_forall(spec, a_to_p_4); - // }); - // let a_to_p_5 = |res: SubResource| lift_state(ZKCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(get_request(res, zookeeper).key)); - // assert_by(spec.entails(always(tla_forall(a_to_p_5))), { - // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_5(sub_resource))) by { - // ZKCluster::lemma_always_object_in_ok_get_resp_is_same_as_etcd_with_same_rv(spec, get_request(sub_resource, zookeeper).key); - // } - // spec_entails_always_tla_forall(spec, a_to_p_5); - // }); - // helper_invariants::lemma_always_stateful_set_in_etcd_satisfies_unchangeable(spec, zookeeper); - // let a_to_p_6 = |sub_resource: SubResource| lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(sub_resource, zookeeper)); - // assert_by(spec.entails(always(tla_forall(a_to_p_6))), { - // assert forall |sub_resource: SubResource| spec.entails(always(#[trigger] a_to_p_6(sub_resource))) by { - // helper_invariants::lemma_always_object_in_etcd_satisfies_unchangeable(spec, sub_resource, zookeeper); - // } - // spec_entails_always_tla_forall(spec, a_to_p_6); - // }); - - // entails_always_and_n!( - // spec, - // lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), - // lift_state(ZKCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()), - // lift_state(ZKCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(zookeeper.object_ref())), - // lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), - // lift_state(ZKCluster::every_in_flight_msg_has_lower_id_than_allocator()), - // lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), - // lift_state(ZKCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), - // lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), - // tla_forall(a_to_p_1), - // lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::Init))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))), - // lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))), - // tla_forall(a_to_p_2), - // tla_forall(a_to_p_3), - // lift_state(helper_invariants::no_update_status_request_msg_not_from_bc_in_flight_of_stateful_set(zookeeper)), - // lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), - // lift_state(ZKCluster::key_of_object_in_matched_ok_get_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), - // lift_state(ZKCluster::key_of_object_in_matched_ok_create_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), - // lift_state(ZKCluster::key_of_object_in_matched_ok_update_resp_message_is_same_as_key_of_pending_req(zookeeper.object_ref())), - // tla_forall(a_to_p_4), - // tla_forall(a_to_p_5), - // lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), - // tla_forall(a_to_p_6) - // ); } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs deleted file mode 100644 index 132ea0022..000000000 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/stateful_set_match.rs +++ /dev/null @@ -1,531 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use crate::external_api::spec::*; -use crate::kubernetes_api_objects::spec::{ - api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, -}; -use crate::kubernetes_cluster::spec::{ - builtin_controllers::types::BuiltinControllerChoice, - cluster::*, - cluster_state_machine::Step, - controller::types::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::temporal_logic::{defs::*, rules::*}; -use crate::vstd_ext::{map_lib::*, string_view::*}; -// use crate::zookeeper_controller::{ -// model::{reconciler::*, resource::*}, -// proof::{helper_invariants, liveness::resource_match::*, predicate::*, resource::*}, -// trusted::{spec_types::*, step::*}, -// }; -use vstd::{prelude::*, string::*}; - -verus! { - -// pub proof fn lemma_from_after_get_stateful_set_step_to_stateful_set_matches( -// spec: TempPred, zookeeper: ZookeeperClusterView -// ) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), -// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), -// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), -// spec.entails(always(lift_state(ZKCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), -// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::every_resource_create_request_implies_at_after_create_resource_step(SubResource::ConfigMap, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), -// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), -// ensures spec.entails(lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)).leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)))), -// { -// lemma_from_after_get_resource_step_and_key_not_exists_to_resource_matches(spec, SubResource::StatefulSet, zookeeper); -// lemma_from_after_get_stateful_set_step_and_key_exists_to_stateful_set_matches(spec, zookeeper); -// let key_not_exists = lift_state(|s: ZKCluster| { -// &&& !s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }); -// let key_exists = lift_state(|s: ZKCluster| { -// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }); -// or_leads_to_combine_temp(spec, key_not_exists, key_exists, lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))); -// temp_pred_equality( -// key_not_exists.or(key_exists), lift_state(pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)) -// ); -// } - -// proof fn lemma_from_after_get_stateful_set_step_and_key_exists_to_stateful_set_matches( -// spec: TempPred, zookeeper: ZookeeperClusterView -// ) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), -// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), -// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), -// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), -// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), -// ensures -// spec.entails( -// lift_state(|s: ZKCluster| { -// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }).leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))) -// ), -// { -// let sts_key = get_request(SubResource::StatefulSet, zookeeper).key; -// let pre = lift_state(|s: ZKCluster| { -// &&& s.resources().contains_key(get_request(SubResource::StatefulSet, zookeeper).key) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }); -// let stateful_set_matches = lift_state(|s: ZKCluster| { -// &&& s.resources().contains_key(sts_key) -// &&& sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }); -// let stateful_set_not_matches = lift_state(|s: ZKCluster| { -// &&& s.resources().contains_key(sts_key) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// &&& pending_req_in_flight_at_after_get_resource_step(SubResource::StatefulSet, zookeeper)(s) -// }); -// let post = lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)); - -// assert_by(spec.entails(stateful_set_matches.leads_to(post)), { -// valid_implies_implies_leads_to(spec, stateful_set_matches, post); -// }); - -// assert_by(spec.entails(stateful_set_not_matches.leads_to(post)), { -// let pre1 = |req_msg| lift_state(|s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }); -// let post1 = lift_state(|s: ZKCluster| { -// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }); -// assert forall |req_msg| spec.entails(#[trigger] pre1(req_msg).leads_to(post1)) by { -// lemma_from_key_exists_to_receives_ok_resp_at_after_get_stateful_set_step(spec, zookeeper, req_msg); -// } -// leads_to_exists_intro(spec, pre1, post1); -// assert_by(tla_exists(pre1) == stateful_set_not_matches, { -// assert forall |ex| #[trigger] stateful_set_not_matches.satisfied_by(ex) -// implies tla_exists(pre1).satisfied_by(ex) by { -// let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); -// assert(pre1(req_msg).satisfied_by(ex)); -// } -// temp_pred_equality(tla_exists(pre1), stateful_set_not_matches); -// }); - -// let pre2 = |resp_msg| lift_state(|s: ZKCluster| { -// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }); -// let post2 = lift_state(|s: ZKCluster| { -// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }); -// assert forall |resp_msg| spec.entails(#[trigger] pre2(resp_msg).leads_to(post2)) by { -// lemma_from_after_get_stateful_set_step_to_after_update_stateful_set_step(spec, zookeeper, resp_msg); -// } -// leads_to_exists_intro(spec, pre2, post2); -// assert_by(tla_exists(pre2) == post1, { -// assert forall |ex| #[trigger] post1.satisfied_by(ex) -// implies tla_exists(pre2).satisfied_by(ex) by { -// let resp_msg = choose |resp_msg| { -// &&& #[trigger] ex.head().in_flight().contains(resp_msg) -// &&& Message::resp_msg_matches_req_msg(resp_msg, ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0()) -// &&& resp_msg.content.get_get_response().res.is_Ok() -// &&& resp_msg.content.get_get_response().res.get_Ok_0() == ex.head().resources()[sts_key] -// }; -// assert(pre2(resp_msg).satisfied_by(ex)); -// } -// temp_pred_equality(tla_exists(pre2), post1); -// }); - -// let pre3 = |req_msg| lift_state(|s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }); -// assert forall |req_msg| spec.entails(#[trigger] pre3(req_msg).leads_to(post)) by { -// lemma_stateful_set_state_matches_at_after_update_stateful_set_step(spec, zookeeper, req_msg); -// } -// leads_to_exists_intro(spec, pre3, post); -// assert_by(tla_exists(pre3) == post2, { -// assert forall |ex| #[trigger] post2.satisfied_by(ex) -// implies tla_exists(pre3).satisfied_by(ex) by { -// let req_msg = ex.head().ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); -// assert(pre3(req_msg).satisfied_by(ex)); -// } -// temp_pred_equality(tla_exists(pre3), post2); -// }); - -// leads_to_trans_n!(spec, stateful_set_not_matches, post1, post2, post); -// }); - -// or_leads_to_combine_temp(spec, stateful_set_matches, stateful_set_not_matches, post); -// temp_pred_equality(stateful_set_matches.or(stateful_set_not_matches), pre); -// } - -// proof fn lemma_from_key_exists_to_receives_ok_resp_at_after_get_stateful_set_step( -// spec: TempPred, zookeeper: ZookeeperClusterView, req_msg: ZKMessage -// ) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), -// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), -// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), -// ensures -// spec.entails( -// lift_state(|s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }) -// .leads_to(lift_state(|s: ZKCluster| { -// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// })) -// ), -// { -// let pre = |s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_get_resource_step_and_key_exists(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }; -// let post = |s: ZKCluster| { -// &&& at_after_get_resource_step_and_exists_ok_resp_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }; -// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; -// let input = Some(req_msg); -// let stronger_next = |s, s_prime: ZKCluster| { -// &&& ZKCluster::next()(s, s_prime) -// &&& ZKCluster::crash_disabled()(s) -// &&& ZKCluster::busy_disabled()(s) -// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) -// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) -// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) -// }; -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), -// lift_action(ZKCluster::next()), -// lift_state(ZKCluster::crash_disabled()), -// lift_state(ZKCluster::busy_disabled()), -// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), -// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), -// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) -// ); - -// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { -// let step = choose |step| ZKCluster::next_step(s, s_prime, step); -// match step { -// Step::ApiServerStep(input) => { -// let req = input.get_Some_0(); -// assert(!resource_delete_request_msg(resource_key)(req)); -// assert(!resource_update_request_msg(resource_key)(req)); -// assert(!resource_update_status_request_msg(resource_key)(req)); -// if input.get_Some_0() == req_msg { -// let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; -// assert({ -// &&& s_prime.in_flight().contains(resp_msg) -// &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) -// &&& resp_msg.content.get_get_response().res.is_Ok() -// &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] -// }); -// assert(post(s_prime)); -// } -// }, -// _ => {} -// } -// } - -// assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) -// implies post(s_prime) by { -// let resp_msg = ZKCluster::handle_get_request_msg(req_msg, s.kubernetes_api_state).1; -// assert({ -// &&& s_prime.in_flight().contains(resp_msg) -// &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) -// &&& resp_msg.content.get_get_response().res.is_Ok() -// &&& resp_msg.content.get_get_response().res.get_Ok_0() == s_prime.resources()[resource_key] -// }); -// } - -// ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api( -// spec, input, stronger_next, ZKCluster::handle_request(), pre, post -// ); -// } - -// proof fn lemma_from_after_get_stateful_set_step_to_after_update_stateful_set_step( -// spec: TempPred, zookeeper: ZookeeperClusterView, resp_msg: ZKMessage -// ) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), -// spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), -// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), -// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), -// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), -// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), -// ensures -// spec.entails( -// lift_state(|s: ZKCluster| { -// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }) -// .leads_to(lift_state(|s: ZKCluster| { -// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// })) -// ), -// { -// let pre = |s: ZKCluster| { -// &&& resp_msg_is_the_in_flight_ok_resp_at_after_get_resource_step(SubResource::StatefulSet, zookeeper, resp_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }; -// let post = |s: ZKCluster| { -// &&& pending_req_in_flight_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }; -// let input = (Some(resp_msg), Some(zookeeper.object_ref())); -// let stronger_next = |s, s_prime: ZKCluster| { -// &&& ZKCluster::next()(s, s_prime) -// &&& ZKCluster::crash_disabled()(s) -// &&& ZKCluster::busy_disabled()(s) -// &&& ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())(s) -// &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) -// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) -// &&& ZKCluster::desired_state_is(zookeeper)(s) -// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) -// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) -// &&& helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)(s) -// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) -// }; - -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), -// lift_action(ZKCluster::next()), -// lift_state(ZKCluster::crash_disabled()), -// lift_state(ZKCluster::busy_disabled()), -// lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())), -// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), -// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), -// lift_state(ZKCluster::desired_state_is(zookeeper)), -// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), -// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), -// lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)), -// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) -// ); - -// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { -// let step = choose |step| ZKCluster::next_step(s, s_prime, step); -// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; -// match step { -// Step::ApiServerStep(input) => { -// let req = input.get_Some_0(); -// assert(!resource_delete_request_msg(resource_key)(req)); -// assert(!resource_update_request_msg(resource_key)(req)); -// assert(!resource_update_status_request_msg(resource_key)(req)); -// }, -// _ => {} -// } -// } -// ZKCluster::lemma_pre_leads_to_post_by_controller( -// spec, input, stronger_next, -// ZKCluster::continue_reconcile(), pre, post -// ); -// } - -// proof fn lemma_stateful_set_state_matches_at_after_update_stateful_set_step(spec: TempPred, zookeeper: ZookeeperClusterView, req_msg: ZKMessage) -// requires -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), -// spec.entails(always(lift_state(ZKCluster::crash_disabled()))), -// spec.entails(always(lift_state(ZKCluster::busy_disabled()))), -// spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), -// spec.entails(always(lift_state(ZKCluster::each_object_in_etcd_is_well_formed()))), -// spec.entails(always(lift_state(ZKCluster::desired_state_is(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)))), -// ensures -// spec.entails( -// lift_state(|s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }) -// .leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))) -// ), -// { -// let pre = |s: ZKCluster| { -// &&& req_msg_is_the_in_flight_pending_req_at_after_update_resource_step(SubResource::StatefulSet, zookeeper, req_msg)(s) -// &&& !sub_resource_state_matches(SubResource::StatefulSet, zookeeper)(s) -// }; -// let post = sub_resource_state_matches(SubResource::StatefulSet, zookeeper); - -// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; -// let input = Some(req_msg); -// let stronger_next = |s, s_prime: ZKCluster| { -// &&& ZKCluster::next()(s, s_prime) -// &&& ZKCluster::crash_disabled()(s) -// &&& ZKCluster::busy_disabled()(s) -// &&& ZKCluster::every_in_flight_msg_has_unique_id()(s) -// &&& ZKCluster::each_object_in_etcd_is_well_formed()(s) -// &&& ZKCluster::desired_state_is(zookeeper)(s) -// &&& helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())(s) -// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)(s) -// &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) -// &&& helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)(s) -// }; -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), -// lift_action(ZKCluster::next()), -// lift_state(ZKCluster::crash_disabled()), -// lift_state(ZKCluster::busy_disabled()), -// lift_state(ZKCluster::every_in_flight_msg_has_unique_id()), -// lift_state(ZKCluster::each_object_in_etcd_is_well_formed()), -// lift_state(ZKCluster::desired_state_is(zookeeper)), -// lift_state(helper_invariants::the_object_in_reconcile_satisfies_state_validation(zookeeper.object_ref())), -// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::stateful_set_not_exists_or_matches_or_no_more_status_update(zookeeper)), -// lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::object_in_etcd_satisfies_unchangeable(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), -// lift_state(helper_invariants::resource_object_only_has_owner_reference_pointing_to_current_cr(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::stateful_set_in_etcd_satisfies_unchangeable(zookeeper)) -// ); - -// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && ZKCluster::kubernetes_api_next().forward(input)(s, s_prime) -// implies post(s_prime) by { -// let pending_msg = s.ongoing_reconciles()[zookeeper.object_ref()].pending_req_msg.get_Some_0(); -// let resp = ZKCluster::handle_update_request_msg(pending_msg, s.kubernetes_api_state).1; -// assert(s_prime.in_flight().contains(resp)); -// StatefulSetView::marshal_preserves_integrity(); -// } - -// assert forall |s, s_prime: ZKCluster| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { -// let step = choose |step| ZKCluster::next_step(s, s_prime, step); -// match step { -// Step::ApiServerStep(input) => { -// let req = input.get_Some_0(); -// assert(!resource_delete_request_msg(resource_key)(req)); -// assert(!resource_update_status_request_msg(resource_key)(req)); -// if resource_update_request_msg(resource_key)(req) {} else {} -// }, -// _ => {} -// } -// } - -// ZKCluster::lemma_pre_leads_to_post_by_kubernetes_api(spec, input, stronger_next, ZKCluster::handle_request(), pre, post); -// } - -// pub proof fn lemma_stateful_set_is_stable( -// spec: TempPred, zookeeper: ZookeeperClusterView, p: TempPred -// ) -// requires -// spec.entails(p.leads_to(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_action(ZKCluster::next()))), -// spec.entails(always(lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)))), -// spec.entails(always(lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)))), -// spec.entails(always(lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)))), -// ensures spec.entails(p.leads_to(always(lift_state(sub_resource_state_matches(SubResource::StatefulSet, zookeeper))))), -// { -// let post = sub_resource_state_matches(SubResource::StatefulSet, zookeeper); -// let resource_key = get_request(SubResource::StatefulSet, zookeeper).key; -// let stronger_next = |s, s_prime: ZKCluster| { -// &&& ZKCluster::next()(s, s_prime) -// &&& helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)(s) -// &&& helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)(s) -// &&& helper_invariants::cm_rv_stays_unchanged(zookeeper)(s, s_prime) -// }; -// combine_spec_entails_always_n!( -// spec, lift_action(stronger_next), -// lift_action(ZKCluster::next()), -// lift_state(helper_invariants::no_delete_resource_request_msg_in_flight(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::every_resource_update_request_implies_at_after_update_resource_step(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::resource_object_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(SubResource::StatefulSet, zookeeper)), -// lift_state(helper_invariants::cm_rv_is_the_same_as_etcd_server_cm_if_cm_updated(zookeeper)), -// lift_action(helper_invariants::cm_rv_stays_unchanged(zookeeper)) -// ); - -// assert forall |s, s_prime: ZKCluster| post(s) && #[trigger] stronger_next(s, s_prime) implies post(s_prime) by { -// StatefulSetView::marshal_preserves_integrity(); -// let step = choose |step| ZKCluster::next_step(s, s_prime, step); -// match step { -// Step::ApiServerStep(input) => { -// let req = input.get_Some_0(); -// assert(!resource_delete_request_msg(resource_key)(req)); -// if resource_update_request_msg(resource_key)(req) {} else {} -// }, -// _ => {} -// } -// } - -// leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); -// } - -} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs index c08187ca2..440b9ec40 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs @@ -12,7 +12,7 @@ use crate::kubernetes_cluster::spec::{ }; use crate::reconciler::spec::reconciler::*; use crate::temporal_logic::{defs::*, rules::*}; -use crate::zookeeper_controller::{ +use crate::v_replica_set_controller::{ model::reconciler::*, proof::predicate::*, trusted::{spec_types::*, step::*}, @@ -21,230 +21,20 @@ use vstd::prelude::*; verus! { -pub proof fn reconcile_eventually_terminates(spec: TempPred, zookeeper: ZookeeperClusterView) +pub proof fn reconcile_eventually_terminates(spec: TempPred, vrs: VReplicaSetView) requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::external_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - spec.entails(always(lift_state(ZKCluster::no_pending_req_msg_at_reconcile_state(zookeeper.object_ref(), |s: ZookeeperReconcileState| s.reconcile_step == ZookeeperReconcileStep::Init)))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet))))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode))))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode))))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode))))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode))))), - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus))))), - spec.entails(always(tla_forall(|step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( - zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) - ))))), - ensures spec.entails(true_pred().leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), + // spec.entails(always(lift_action(VRSCluster::next()))), + // spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + // spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + // spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + // spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + // spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + // spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + // spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + // spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), + ensures spec.entails(true_pred().leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), { - assert forall |action: ActionKind, sub_resource: SubResource| #![auto] - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( - zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource)) - )))) by { - always_tla_forall_apply::( - spec, |step: (ActionKind, SubResource)| lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( - zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(step.0, step.1)) - )), (action, sub_resource) - ); - } - let reconcile_idle = |s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }; - - // First, prove that reconcile_done \/ reconcile_error \/ reconcile_ide ~> reconcile_idle. - // Here we simply apply a cluster lemma which uses the wf1 of end_reconcile action. - ZKCluster::lemma_reconcile_error_leads_to_reconcile_idle(spec, zookeeper.object_ref()); - ZKCluster::lemma_reconcile_done_leads_to_reconcile_idle(spec, zookeeper.object_ref()); - temp_pred_equality(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), lift_state(ZKCluster::reconciler_reconcile_done(zookeeper.object_ref()))); - temp_pred_equality(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)), lift_state(ZKCluster::reconciler_reconcile_error(zookeeper.object_ref()))); - valid_implies_implies_leads_to(spec, lift_state(reconcile_idle), lift_state(reconcile_idle)); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_state_pred(zookeeper, ZookeeperReconcileStep::Done, ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterUpdateStatus), - at_step1_or_step2_closure(ZookeeperReconcileStep::Done, ZookeeperReconcileStep::Error) - ); - lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::StatefulSet, ZookeeperReconcileStep::AfterUpdateStatus); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterUpdateZKNode), - at_step1_or_step2_closure(after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) - ); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterCreateZKNode), - at_step1_or_step2_closure(after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) - ); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode, ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterCreateZKParentNode), - at_step1_or_step2_closure(ZookeeperReconcileStep::AfterCreateZKNode, ZookeeperReconcileStep::Error) - ); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_or_step3_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode, ZookeeperReconcileStep::AfterCreateZKParentNode, ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKParentNode)), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterExistsZKNode), - at_step1_or_step2_or_step3_closure(ZookeeperReconcileStep::AfterUpdateZKNode, ZookeeperReconcileStep::AfterCreateZKParentNode, ZookeeperReconcileStep::Error) - ); - - or_leads_to_combine_and_equality!(spec, - lift_state(at_step1_or_step2_or_step3_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode)), lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(SubResource::StatefulSet))), lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( - spec, zookeeper, at_step_closure(ZookeeperReconcileStep::AfterExistsStatefulSet), - at_step1_or_step2_or_step3_closure(ZookeeperReconcileStep::AfterExistsZKNode, after_get_k_request_step(SubResource::StatefulSet), ZookeeperReconcileStep::Error) - ); - - lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::ConfigMap, ZookeeperReconcileStep::AfterExistsStatefulSet); - lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::AdminServerService, after_get_k_request_step(SubResource::ConfigMap)); - lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::ClientService, after_get_k_request_step(SubResource::AdminServerService)); - lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle(spec, zookeeper, SubResource::HeadlessService, after_get_k_request_step(SubResource::ClientService)); - - ZKCluster::lemma_from_init_state_to_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(ZookeeperReconcileStep::Init), at_step_closure(after_get_k_request_step(SubResource::HeadlessService))); - - // Finally, combine all cases. - or_leads_to_combine_and_equality!( - spec, - true_pred(), - lift_state(reconcile_idle), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Init)), - lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::HeadlessService)), - lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::ClientService)), - lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::AdminServerService)), - lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::ConfigMap)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsStatefulSet)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterExistsZKNode)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKParentNode)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterCreateZKNode)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateZKNode)), - lift_state(state_pred_regarding_sub_resource(zookeeper, SubResource::StatefulSet)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::AfterUpdateStatus)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Done)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(reconcile_idle) - ); -} - -pub open spec fn at_step1_or_step2_closure(step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep) -> spec_fn(ZookeeperReconcileState) -> bool { - |s: ZookeeperReconcileState| s.reconcile_step == step1 || s.reconcile_step == step2 -} - -pub open spec fn at_step1_or_step2_or_step3_closure(step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep, step3: ZookeeperReconcileStep) -> spec_fn(ZookeeperReconcileState) -> bool { - |s: ZookeeperReconcileState| s.reconcile_step == step1 || s.reconcile_step == step2 || s.reconcile_step == step3 -} - -pub open spec fn at_step_state_pred(zookeeper: ZookeeperClusterView, step: ZookeeperReconcileStep) -> StatePred { - ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step_closure(step)) -} - -pub open spec fn at_step1_or_step2_state_pred(zookeeper: ZookeeperClusterView, step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep) -> StatePred { - ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step1_or_step2_closure(step1, step2)) -} - -pub open spec fn at_step1_or_step2_or_step3_state_pred(zookeeper: ZookeeperClusterView, step1: ZookeeperReconcileStep, step2: ZookeeperReconcileStep, step3: ZookeeperReconcileStep) -> StatePred { - ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), at_step1_or_step2_or_step3_closure(step1, step2, step3)) -} - -pub open spec fn state_pred_regarding_sub_resource(zookeeper: ZookeeperClusterView, sub_resource: SubResource) -> StatePred { - ZKCluster::at_expected_reconcile_states( - zookeeper.object_ref(), - |s: ZookeeperReconcileState| s.reconcile_step.is_AfterKRequestStep() && s.reconcile_step.get_AfterKRequestStep_1() == sub_resource - ) -} - -proof fn lemma_from_after_get_resource_step_to_after_get_next_resource_step_to_reconcile_idle( - spec: TempPred, zookeeper: ZookeeperClusterView, sub_resource: SubResource, next_step: ZookeeperReconcileStep -) - requires - spec.entails(always(lift_action(ZKCluster::next()))), - spec.entails(tla_forall(|i| ZKCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::external_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| ZKCluster::controller_next().weak_fairness(i))), - spec.entails(always(lift_state(ZKCluster::crash_disabled()))), - spec.entails(always(lift_state(ZKCluster::busy_disabled()))), - spec.entails(always(lift_state(ZKCluster::every_in_flight_msg_has_unique_id()))), - spec.entails(always(lift_state(ZKCluster::pending_req_of_key_is_unique_with_unique_id(zookeeper.object_ref())))), - // Ensures that after get/create/update the sub resource, there is always a pending request or matched response - // in flight so that the reconciler can enter the next state. - forall |action: ActionKind| #![auto] - spec.entails(always(lift_state(ZKCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( - zookeeper.object_ref(), at_step_closure(ZookeeperReconcileStep::AfterKRequestStep(action, sub_resource) - ))))), - // Ensures that after successfully creating or updating the sub resource, the reconcile will go to after get next - // sub resource step. - next_resource_after(sub_resource) == next_step, - spec.entails(lift_state(at_step_state_pred(zookeeper, next_step)) - .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), - spec.entails(lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)) - .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), - ensures - spec.entails(lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(sub_resource))) - .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), - spec.entails(lift_state(state_pred_regarding_sub_resource(zookeeper, sub_resource)) - .leads_to(lift_state(|s: ZKCluster| !s.ongoing_reconciles().contains_key(zookeeper.object_ref())))), -{ - let state_after_create_or_update = |s: ZookeeperReconcileState| { - s.reconcile_step == next_step - || s.reconcile_step == ZookeeperReconcileStep::Error - }; - or_leads_to_combine_and_equality!( - spec, lift_state(ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), state_after_create_or_update)), - lift_state(at_step_state_pred(zookeeper, next_step)), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_create_k_request_step(sub_resource)), state_after_create_or_update); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_update_k_request_step(sub_resource)), state_after_create_or_update); - - let state_after_get = |s: ZookeeperReconcileState| { - s.reconcile_step == after_create_k_request_step(sub_resource) - || s.reconcile_step == after_update_k_request_step(sub_resource) - || s.reconcile_step == ZookeeperReconcileStep::Error - }; - or_leads_to_combine_and_equality!( - spec, lift_state(ZKCluster::at_expected_reconcile_states(zookeeper.object_ref(), state_after_get)), - lift_state(at_step_state_pred(zookeeper, after_create_k_request_step(sub_resource))), - lift_state(at_step_state_pred(zookeeper, after_update_k_request_step(sub_resource))), - lift_state(at_step_state_pred(zookeeper, ZookeeperReconcileStep::Error)); - lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) - ); - ZKCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, zookeeper, at_step_closure(after_get_k_request_step(sub_resource)), state_after_get); - or_leads_to_combine_and_equality!( - spec, lift_state(state_pred_regarding_sub_resource(zookeeper, sub_resource)), - lift_state(at_step_state_pred(zookeeper, after_get_k_request_step(sub_resource))), - lift_state(at_step_state_pred(zookeeper, after_create_k_request_step(sub_resource))), - lift_state(at_step_state_pred(zookeeper, after_update_k_request_step(sub_resource))); - lift_state(|s: ZKCluster| { !s.ongoing_reconciles().contains_key(zookeeper.object_ref()) }) - ); + assume(false); } } diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index 39eb97c51..a99737ef3 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -20,4 +20,98 @@ use vstd::prelude::*; verus! { +pub open spec fn at_step_closure(step: VReplicaSetReconcileStep) -> spec_fn(VReplicaSetReconcileState) -> bool { + |s: VReplicaSetReconcileState| s.reconcile_step == step +} + +pub open spec fn at_vrs_step_with_vrs(vrs: VReplicaSetView, step: VReplicaSetReconcileStep) -> StatePred { + |s: VRSCluster| { + &&& s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.ongoing_reconciles()[vrs.object_ref()].triggering_cr.object_ref() == vrs.object_ref() + &&& s.ongoing_reconciles()[vrs.object_ref()].triggering_cr.spec() == vrs.spec() + &&& s.ongoing_reconciles()[vrs.object_ref()].triggering_cr.metadata().uid == vrs.metadata().uid + &&& s.ongoing_reconciles()[vrs.object_ref()].local_state.reconcile_step == step + } +} + +pub open spec fn pending_req_in_flight_at_after_list_pods_step( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterListPods; + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(msg) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_ListRequest() + &&& request.get_ListRequest_0() == ListRequest { + kind: PodView::kind(), + namespace: vrs.metadata.namespace.unwrap(), + } + } +} + +pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> Set { + Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)) +} + +pub open spec fn num_fewer_pods_is(vrs: VReplicaSetView, resources: StoredState, diff: nat) -> bool { + let pods = matching_pods(vrs, resources); + &&& pods.finite() + &&& (vrs.spec.replicas.unwrap_or(0) - pods.len()) as nat == diff +} + +pub open spec fn num_more_pods_is(vrs: VReplicaSetView, resources: StoredState, diff: nat) -> bool { + let pods = matching_pods(vrs, resources); + &&& pods.finite() + &&& (pods.len() - vrs.spec.replicas.unwrap_or(0)) as nat == diff +} + +pub open spec fn pending_req_in_flight_at_after_create_pod_step( + vrs: VReplicaSetView, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterCreatePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(msg) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_CreateRequest() + &&& request.get_CreateRequest_0() == CreateRequest { + namespace: vrs.metadata.namespace.unwrap(), + obj: make_pod(vrs).marshal(), + } + } +} + +pub open spec fn pending_req_in_flight_at_after_delete_pod_step( + vrs: VReplicaSetView, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterDeletePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(msg) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_DeleteRequest() + // TODO: Specify this predicate further. + // &&& request.get_CreateRequest_0() == CreateRequest { + // namespace: namespace, + // obj: make_pod(vrs).marshal(), + // } + } +} + } diff --git a/src/controller_examples/v_replica_set_controller/trusted/exec_types.rs b/src/controller_examples/v_replica_set_controller/trusted/exec_types.rs index 8e971478b..cf2c4de98 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/exec_types.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/exec_types.rs @@ -39,7 +39,7 @@ impl View for VReplicaSetReconcileState { open spec fn view(&self) -> spec_types::VReplicaSetReconcileState { spec_types::VReplicaSetReconcileState { - reconcile_step: self.reconcile_step@, + reconcile_step: self.reconcile_step, filtered_pods: match self.filtered_pods { Some(fp) => Some(fp@.map_values(|p: Pod| p@)), None => None, diff --git a/src/controller_examples/v_replica_set_controller/trusted/spec_types.rs b/src/controller_examples/v_replica_set_controller/trusted/spec_types.rs index 464e70af8..ef4b720d9 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/spec_types.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/spec_types.rs @@ -21,7 +21,7 @@ pub type VRSMessage = Message; pub struct VReplicaSetReconciler {} pub struct VReplicaSetReconcileState { - pub reconcile_step: VReplicaSetReconcileStepView, + pub reconcile_step: VReplicaSetReconcileStep, pub filtered_pods: Option>, } diff --git a/src/controller_examples/v_replica_set_controller/trusted/step.rs b/src/controller_examples/v_replica_set_controller/trusted/step.rs index 01b5c8f0f..d988c1417 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/step.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/step.rs @@ -24,30 +24,4 @@ impl std::clone::Clone for VReplicaSetReconcileStep { { *self } } -impl View for VReplicaSetReconcileStep { - type V = VReplicaSetReconcileStepView; - - open spec fn view(&self) -> VReplicaSetReconcileStepView { - match self { - VReplicaSetReconcileStep::Init => VReplicaSetReconcileStepView::Init, - VReplicaSetReconcileStep::AfterListPods => VReplicaSetReconcileStepView::AfterListPods, - VReplicaSetReconcileStep::AfterCreatePod(i) => VReplicaSetReconcileStepView::AfterCreatePod(*i as nat), - VReplicaSetReconcileStep::AfterDeletePod(i) => VReplicaSetReconcileStepView::AfterDeletePod(*i as nat), - VReplicaSetReconcileStep::Done => VReplicaSetReconcileStepView::Done, - VReplicaSetReconcileStep::Error => VReplicaSetReconcileStepView::Error, - } - } -} - -#[is_variant] -pub enum VReplicaSetReconcileStepView { - Init, - AfterListPods, - AfterCreatePod(nat), - AfterDeletePod(nat), - Done, - Error, -} - - } From c4288b7a676d27d22564be1c677553dd4bc9eb49 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Mon, 12 Aug 2024 18:27:31 -0500 Subject: [PATCH 03/22] Add ranking function reasoning for leads-to Signed-off-by: Cody Rivera --- src/temporal_logic/rules.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/temporal_logic/rules.rs b/src/temporal_logic/rules.rs index b2deebc7a..81aef1dce 100644 --- a/src/temporal_logic/rules.rs +++ b/src/temporal_logic/rules.rs @@ -1935,4 +1935,41 @@ pub proof fn leads_to_shortcut_temp(spec: TempPred, p: TempPred, q: Tem leads_to_trans_temp(spec, p, q.or(s), r.or(s)); } +/// Concluding p(n) ~> p(0) using ranking functions, with a step of one. +/// pre: +/// forall |n: nat|, n > 0 ==> spec |= p(n) ~> p(n - 1) +/// post: +/// forall |n: nat|, spec |= p(n) ~> p(0). +pub proof fn leads_to_rank_step_one(spec: TempPred, p: spec_fn(nat) -> TempPred) + requires + forall |n: nat| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as nat)))), + ensures + forall |n: nat| #[trigger] spec.entails(p(n).leads_to(p(0))), +{ + let pre = { + forall |n: nat| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as nat)))) + }; + assert forall |n: nat| pre implies #[trigger] spec.entails(p(n).leads_to(p(0))) by { + leads_to_rank_step_one_help(spec, p, n); + } +} + +pub proof fn leads_to_rank_step_one_help(spec: TempPred, p: spec_fn(nat) -> TempPred, n: nat) + requires + forall |n: nat| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as nat)))), + ensures + spec.entails(p(n).leads_to(p(0))), + decreases n, +{ + if n > 0 { + // p(n) ~> p(n - 1), p(n - 1) ~> p(0) + // combine with leads-to transitivity + leads_to_rank_step_one_help(spec, p, (n - 1) as nat); + leads_to_trans_n!(spec, p(n), p((n - 1) as nat), p(0)); + } else { + // p(0) ~> p(0) trivially + leads_to_self_temp(p(0)); + } +} + } From e2969e102f109caf003069cf121bfaad2f66ad70 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 13 Aug 2024 09:30:10 -0500 Subject: [PATCH 04/22] Set lemma to private Signed-off-by: Cody Rivera --- src/temporal_logic/rules.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/temporal_logic/rules.rs b/src/temporal_logic/rules.rs index 81aef1dce..c21e848ff 100644 --- a/src/temporal_logic/rules.rs +++ b/src/temporal_logic/rules.rs @@ -1954,7 +1954,7 @@ pub proof fn leads_to_rank_step_one(spec: TempPred, p: spec_fn(nat) -> Tem } } -pub proof fn leads_to_rank_step_one_help(spec: TempPred, p: spec_fn(nat) -> TempPred, n: nat) +proof fn leads_to_rank_step_one_help(spec: TempPred, p: spec_fn(nat) -> TempPred, n: nat) requires forall |n: nat| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as nat)))), ensures From 00b1c6be85e226c55850815a7bf642c99851bf4e Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 13 Aug 2024 14:07:46 -0500 Subject: [PATCH 05/22] Work in progress for p(diff) ~> matches Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 363 ++++++++++++++---- .../proof/predicate.rs | 243 +++++++++++- 2 files changed, 520 insertions(+), 86 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 00d22e4a5..303f69708 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -19,103 +19,328 @@ use crate::v_replica_set_controller::{ proof::{helper_invariants, predicate::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, string::*}; +use vstd::{prelude::*, string::*, math::abs}; verus! { -#[verifier(external_body)] -pub proof fn lemma_from_after_list_pods_step_to_after_scale_pod_step_or_done( - spec: TempPred, vrs: VReplicaSetView, diff: nat, +pub proof fn lemma_from_diff_and_init_to_current_state_matches( + spec: TempPred, vrs: VReplicaSetView, diff: int, ) ensures - // Case 1: Number of matching pods is less than the replica count. - diff > 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), diff) - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) - &&& pending_req_in_flight_at_after_create_pod_step(vrs, (diff - 1) as nat)(s) - })) + spec.entails( + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to(lift_state(current_state_matches(vrs))) + ) +{ + // Deal with transition from init to pod listings. + lemma_from_init_step_to_send_list_pods_req(spec, vrs, diff); + lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp(spec, vrs, diff); + + // TODO: Would an invariant be a better way of showing + // num_diff_pods_is is maintained across this introductory step? + leads_to_trans_n!( + spec, + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } ), - // Case 2: Number of matching pods is greater than the replica count. - diff > 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_more_pods_is(vrs, s.resources(), diff) + lift_state( + |s: VRSCluster| { &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& num_more_pods_is(vrs, s.resources(), (diff - 1) as nat) - &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) - })) + &&& num_diff_pods_is(vrs, diff)(s) + } ), - // Case 3: Number of matching pods equals the replica count. + lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ); + + let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Dispatch by difference. + if diff < 0 { + // // Predicates specific to creating pods. + // let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { + // &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + // &&& num_diff_pods_is(vrs, diff)(s) + // }); + // let create_req = |diff: int| lift_state( + // |s: VRSCluster| { + // &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + // &&& num_diff_pods_is(vrs, diff)(s) + // } + // ); + // let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + // |s: VRSCluster| { + // &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + // &&& num_diff_pods_is(vrs, diff)(s) + // } + // ); + // let create_resp = |diff: int| lift_state( + // |s: VRSCluster| { + // &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) + // &&& num_diff_pods_is(vrs, diff)(s) + // } + // ) + + // // Try creating a pod starting in the AfterListPods step. + // assert forall |resp_msg: VRSMessage| + // #[trigger] spec.entails( + // // lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req( + // // spec, vrs, resp_msg, diff + // // ); + // // lemma_from_after_send_create_pod_req_to_recieve_ok_resp( + // // spec, vrs, resp_msg, diff + // // ); + // //leads_to_trans_n!(spec, ) + + assume(false); + } else if diff > 0 { + assume(false); + } else { + // diff = 0 + // Use p ~> q, weakening of q => q'. + leads_to_weaken_temp( + spec, + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ), + lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ), + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ), + lift_state(current_state_matches(vrs)) + ); + } +} + +#[verifier(external_body)] +pub proof fn lemma_from_init_step_to_send_list_pods_req( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + ensures spec.entails( - lift_state(|s: VRSCluster| { - &&& num_more_pods_is(vrs, s.resources(), 0) - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& resource_state_matches(vrs, s.resources()) - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) - })) + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) + ) +{ +} + +#[verifier(external_body)] +pub proof fn lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) + ) +{ +} + +// If there are too few pods, these lemmas apply. +#[verifier(external_body)] +pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req( + spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) ), { } #[verifier(external_body)] -pub proof fn lemma_from_after_create_pod_step_to_after_create_pod_step_or_done( - spec: TempPred, vrs: VReplicaSetView, diff: nat, +pub proof fn lemma_from_after_send_create_pod_req_to_recieve_ok_resp( + spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) + requires + diff < 0, ensures - // Case 1: Number of matching pods is less than the replica count. - diff > 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), diff) - &&& pending_req_in_flight_at_after_create_pod_step(vrs, diff)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) - &&& pending_req_in_flight_at_after_create_pod_step(vrs, (diff - 1) as nat)(s) - })) + spec.entails( + lift_state( + |s: VRSCluster| { + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff + 1)(s) + } + ) + ) ), - // Case 2: No more pods to create. - diff == 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), 0) - &&& pending_req_in_flight_at_after_create_pod_step(vrs, 0)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& resource_state_matches(vrs, s.resources()) - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) - })) +{ +} + +#[verifier(external_body)] +pub proof fn lemma_from_after_recieve_ok_resp_to_send_create_pod_req( + spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) ), { } +// If there are too many pods, this lemma applies. #[verifier(external_body)] -pub proof fn lemma_from_after_delete_pod_step_to_after_delete_pod_step_or_done( - spec: TempPred, vrs: VReplicaSetView, diff: nat, +pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_delete_pod_req( + spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) + requires + diff > 0, ensures - // Case 1: Number of matching pods is less than the replica count. - diff > 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_more_pods_is(vrs, s.resources(), diff) - &&& pending_req_in_flight_at_after_delete_pod_step(vrs, diff)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& num_fewer_pods_is(vrs, s.resources(), (diff - 1) as nat) - &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) - })) + spec.entails( + lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) ), - // Case 2: No more pods to create. - diff == 0 ==> spec.entails( - lift_state(|s: VRSCluster| { - &&& num_more_pods_is(vrs, s.resources(), 0) - &&& pending_req_in_flight_at_after_delete_pod_step(vrs, 0)(s) - }).leads_to(lift_state(|s: VRSCluster| { - &&& resource_state_matches(vrs, s.resources()) - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Done)(s) - })) +{ +} + +#[verifier(external_body)] +pub proof fn lemma_from_after_send_delete_pod_req_to_recieve_ok_resp( + spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& req_msg_is_the_in_flight_delete_request_at_after_delete_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff - 1)(s) + } + ) + ) ), { } +#[verifier(external_body)] +pub proof fn lemma_from_after_recieve_ok_resp_to_send_delete_pod_req( + spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) + ), +{ +} } diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index a99737ef3..54bfecc74 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -16,10 +16,12 @@ use crate::v_replica_set_controller::model::{reconciler::*}; use crate::v_replica_set_controller::trusted::{ liveness_theorem::*, spec_types::*, step::*, }; +use vstd::math::abs; use vstd::prelude::*; verus! { +// Predicates for reasoning about model states pub open spec fn at_step_closure(step: VReplicaSetReconcileStep) -> spec_fn(VReplicaSetReconcileState) -> bool { |s: VReplicaSetReconcileState| s.reconcile_step == step } @@ -34,6 +36,20 @@ pub open spec fn at_vrs_step_with_vrs(vrs: VReplicaSetView, step: VReplicaSetRec } } +// Predicates for reasoning about pods +pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> Set { + Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)) +} + +pub open spec fn num_diff_pods_is(vrs: VReplicaSetView, diff: int) -> StatePred { + |s: VRSCluster| { + let pods = matching_pods(vrs, s.resources()); + &&& pods.finite() + &&& pods.len() - vrs.spec.replicas.unwrap_or(0) == diff + } +} + +// Predicates to specify leads-to boundary lemmas. pub open spec fn pending_req_in_flight_at_after_list_pods_step( vrs: VReplicaSetView ) -> StatePred { @@ -55,22 +71,78 @@ pub open spec fn pending_req_in_flight_at_after_list_pods_step( } } -pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> Set { - Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)) -} - -pub open spec fn num_fewer_pods_is(vrs: VReplicaSetView, resources: StoredState, diff: nat) -> bool { - let pods = matching_pods(vrs, resources); - &&& pods.finite() - &&& (vrs.spec.replicas.unwrap_or(0) - pods.len()) as nat == diff +pub open spec fn exists_resp_in_flight_at_after_list_pods_step( + vrs: VReplicaSetView, +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterListPods; + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(msg) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_ListRequest() + &&& request.get_ListRequest_0() == ListRequest { + kind: PodView::kind(), + namespace: vrs.metadata.namespace.unwrap(), + } + &&& exists |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + } + } } -pub open spec fn num_more_pods_is(vrs: VReplicaSetView, resources: StoredState, diff: nat) -> bool { - let pods = matching_pods(vrs, resources); - &&& pods.finite() - &&& (pods.len() - vrs.spec.replicas.unwrap_or(0)) as nat == diff +pub open spec fn resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step( + vrs: VReplicaSetView, resp_msg: VRSMessage +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterListPods; + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_ListRequest() + &&& request.get_ListRequest_0() == ListRequest { + kind: PodView::kind(), + namespace: vrs.metadata.namespace.unwrap(), + } + &&& s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + } } +// Pod creation predicates pub open spec fn pending_req_in_flight_at_after_create_pod_step( vrs: VReplicaSetView, diff: nat ) -> StatePred { @@ -92,6 +164,88 @@ pub open spec fn pending_req_in_flight_at_after_create_pod_step( } } +pub open spec fn req_msg_is_the_in_flight_create_request_at_after_create_pod_step( + vrs: VReplicaSetView, req_msg: VRSMessage, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterCreatePod(diff as usize); + let request = req_msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(req_msg) + &&& req_msg.src == HostId::CustomController + &&& req_msg.dst == HostId::ApiServer + &&& req_msg.content.is_APIRequest() + &&& request.is_CreateRequest() + &&& request.get_CreateRequest_0() == CreateRequest { + namespace: vrs.metadata.namespace.unwrap(), + obj: make_pod(vrs).marshal(), + } + } +} + +pub open spec fn exists_ok_resp_in_flight_at_after_create_pod_step( + vrs: VReplicaSetView, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterCreatePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_CreateRequest() + &&& request.get_CreateRequest_0() == CreateRequest { + namespace: vrs.metadata.namespace.unwrap(), + obj: make_pod(vrs).marshal(), + } + &&& exists |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + } + } +} + +pub open spec fn resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step( + vrs: VReplicaSetView, resp_msg: VRSMessage, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterCreatePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_CreateRequest() + &&& request.get_CreateRequest_0() == CreateRequest { + namespace: vrs.metadata.namespace.unwrap(), + obj: make_pod(vrs).marshal(), + } + &&& s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + } +} + +// Pod deletion predicates + +// Placeholder predicate constraining the delete request +// We'll probably need something here ensuring we only delete the +// appropriate keys: this will facilitate modifications by a search-and-replace. +pub open spec fn delete_constraint( + vrs: VReplicaSetView, req: DeleteRequest +) -> StatePred { + |s: VRSCluster| { + true // placeholder + } +} + + pub open spec fn pending_req_in_flight_at_after_delete_pod_step( vrs: VReplicaSetView, diff: nat ) -> StatePred { @@ -106,11 +260,66 @@ pub open spec fn pending_req_in_flight_at_after_delete_pod_step( &&& msg.dst == HostId::ApiServer &&& msg.content.is_APIRequest() &&& request.is_DeleteRequest() - // TODO: Specify this predicate further. - // &&& request.get_CreateRequest_0() == CreateRequest { - // namespace: namespace, - // obj: make_pod(vrs).marshal(), - // } + &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) + } +} + +pub open spec fn req_msg_is_the_in_flight_delete_request_at_after_delete_pod_step( + vrs: VReplicaSetView, req_msg: VRSMessage, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterDeletePod(diff as usize); + let request = req_msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& s.in_flight().contains(req_msg) + &&& req_msg.src == HostId::CustomController + &&& req_msg.dst == HostId::ApiServer + &&& req_msg.content.is_APIRequest() + &&& request.is_DeleteRequest() + &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) + } +} + +pub open spec fn exists_ok_resp_in_flight_at_after_delete_pod_step( + vrs: VReplicaSetView, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterDeletePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_DeleteRequest() + &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) + &&& exists |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_delete_response().res.is_Ok() + } + } +} + +pub open spec fn resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step( + vrs: VReplicaSetView, resp_msg: VRSMessage, diff: nat +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterDeletePod(diff as usize); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_DeleteRequest() + &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) + &&& s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_delete_response().res.is_Ok() } } From 66b4319ea763cb5e0c23f35835887303477ec319 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 13 Aug 2024 16:23:47 -0500 Subject: [PATCH 06/22] Add case where there is one fewer pod than the spec Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 201 +++++++++++------- 1 file changed, 123 insertions(+), 78 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 303f69708..095bf390e 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -36,7 +36,26 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( ).leads_to(lift_state(current_state_matches(vrs))) ) { - // Deal with transition from init to pod listings. + let pre = |diff: int| lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Deal with transition from init to listing the pods. lemma_from_init_step_to_send_list_pods_req(spec, vrs, diff); lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp(spec, vrs, diff); @@ -44,102 +63,128 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( // num_diff_pods_is is maintained across this introductory step? leads_to_trans_n!( spec, + pre(diff), lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) &&& num_diff_pods_is(vrs, diff)(s) } ), - lift_state( + list_resp(diff) + ); + + // Now we've listed the pods, perform different behaviors accoding to the difference. + if diff < 0 { + // Predicates specific to creating pods. + let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }); + let create_req = |diff: int| lift_state( |s: VRSCluster| { - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } - ), - lift_state( + ); + let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( |s: VRSCluster| { - &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) &&& num_diff_pods_is(vrs, diff)(s) } - ) - ); + ); + let create_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); - let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( - |s: VRSCluster| { - &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ); - let list_resp = |diff: int| lift_state( - |s: VRSCluster| { - &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ); + // Apply two lemmas relating to the first created pod. + assert forall |resp_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { + lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { + lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); + }; - // Dispatch by difference. - if diff < 0 { - // // Predicates specific to creating pods. - // let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { - // &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) - // &&& num_diff_pods_is(vrs, diff)(s) - // }); - // let create_req = |diff: int| lift_state( - // |s: VRSCluster| { - // &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) - // &&& num_diff_pods_is(vrs, diff)(s) - // } - // ); - // let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( - // |s: VRSCluster| { - // &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) - // &&& num_diff_pods_is(vrs, diff)(s) - // } - // ); - // let create_resp = |diff: int| lift_state( - // |s: VRSCluster| { - // &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) - // &&& num_diff_pods_is(vrs, diff)(s) - // } - // ) + // Attach the two lemmas to the transitivity chain. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), create_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); + assert_by( + spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + }; + assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); + } + ); + assert_by( + spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); + } + ); + leads_to_trans_n!( + spec, + pre(diff), + list_resp(diff), + tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), + create_req(diff), + tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), + create_resp(diff + 1) + ); - // // Try creating a pod starting in the AfterListPods step. - // assert forall |resp_msg: VRSMessage| - // #[trigger] spec.entails( - // // lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req( - // // spec, vrs, resp_msg, diff - // // ); - // // lemma_from_after_send_create_pod_req_to_recieve_ok_resp( - // // spec, vrs, resp_msg, diff - // // ); - // //leads_to_trans_n!(spec, ) + // Is the create request after listing the pods enough? + if diff + 1 == 0 { + // If so, pre(diff) ~> current_state_matches(vrs) trivially. + valid_implies_implies_leads_to(spec, create_resp(diff + 1), lift_state(current_state_matches(vrs))); + leads_to_trans_n!( + spec, + pre(diff), + create_resp(diff + 1), + lift_state(current_state_matches(vrs)) + ); + return; + } - assume(false); + assume(false); // Not proven yet!! } else if diff > 0 { assume(false); } else { // diff = 0 - // Use p ~> q, weakening of q => q'. - leads_to_weaken_temp( - spec, - lift_state( - |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ), - lift_state( - |s: VRSCluster| { - &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ), - lift_state( - |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ), + // list_resp(diff) ~> current_state_matches(vrs) trivially. + valid_implies_implies_leads_to(spec, list_resp(diff), lift_state(current_state_matches(vrs))); + leads_to_trans_n!( + spec, + pre(diff), + list_resp(diff), lift_state(current_state_matches(vrs)) ); } From 352de4fdc945e64521b1ca997fd6889ae8f3b13b Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 13 Aug 2024 17:51:51 -0500 Subject: [PATCH 07/22] Finish case where there are too few pods Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 221 ++++++++++++------ .../proof/predicate.rs | 3 +- 2 files changed, 157 insertions(+), 67 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 095bf390e..2b1a9a97c 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -99,67 +99,69 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( } ); - // Apply two lemmas relating to the first created pod. - assert forall |resp_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { - lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); - }; - assert forall |req_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { - lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); - }; + assert_by(diff < 0 ==> spec.entails(pre(diff).leads_to(create_resp(diff + 1))), { + // Apply two lemmas relating to the first created pod. + assert forall |resp_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { + lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { + lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); + }; - // Attach the two lemmas to the transitivity chain. - leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), create_req(diff)); - leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); - assert_by( - spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), - { - assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) - implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { - let s = ex.head(); - let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - let resp_msg = choose |resp_msg| { - &&& #[trigger] s.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, msg) - &&& resp_msg.content.get_list_response().res.is_Ok() - &&& { - let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() - } - ) - } + // Attach the two lemmas to the transitivity chain. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), create_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); + assert_by( + spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + }; + assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); }; - assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); - } - ); - assert_by( - spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), - { - assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) - implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); - } - ); - leads_to_trans_n!( - spec, - pre(diff), - list_resp(diff), - tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), - create_req(diff), - tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), - create_resp(diff + 1) - ); + valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); + } + ); + assert_by( + spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); + } + ); + leads_to_trans_n!( + spec, + pre(diff), + list_resp(diff), + tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), + create_req(diff), + tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), + create_resp(diff + 1) + ); + }); // Is the create request after listing the pods enough? if diff + 1 == 0 { @@ -174,9 +176,98 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( return; } - assume(false); // Not proven yet!! + // In this branch, we need to convert negative integers to natural numbers for + // the predicate to fit the rule leads_to_rank_step_one. + let ranking_pred = |n: nat| create_resp(-n); + + // Useful assertions to let us chain in and out of ranking_pred + assert forall |n: nat| #![trigger create_resp(-n)] + spec.entails(create_resp(-n).leads_to(ranking_pred(n))) by { + valid_implies_implies_leads_to(spec, create_resp(-n), ranking_pred(n)); + }; + + assert forall |n: nat| #![trigger create_resp(-n)] + spec.entails(ranking_pred(n).leads_to(create_resp(-n))) by { + valid_implies_implies_leads_to(spec, ranking_pred(n), create_resp(-n)); + }; + + // Proving n > 0 => ranking_pred(n) ~> ranking_pred(n - 1) + assert forall |n: nat| #![trigger ranking_pred(n)] + n > 0 implies spec.entails(ranking_pred(n).leads_to(ranking_pred((n - 1) as nat))) by { + let diff = -n; + + // Apply two lemmas relating to subsequent created pods. + assert forall |resp_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { + lemma_from_after_recieve_ok_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { + lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); + }; + + // Chain the lemmas by transitivity. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| create_resp_msg(resp_msg, diff), create_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); + assert_by( + spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); + } + ); + + // Chain out of and into our ranking predicate. + assert_by( + spec.entails(create_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] create_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + }; + assert((|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_resp(diff), tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))); + } + ); + + leads_to_trans_n!( + spec, + ranking_pred(n), + create_resp(diff), + tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)), + create_req(diff), + tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), + create_resp(diff + 1), + ranking_pred((n - 1) as nat) + ); + }; + + // Apply ranking function lemma + leads_to_rank_step_one(spec, ranking_pred); + + // Chain everything + valid_implies_implies_leads_to(spec, create_resp(0), lift_state(current_state_matches(vrs))); + leads_to_trans_n!( + spec, + pre(diff), + create_resp(diff + 1), + ranking_pred(-(diff + 1) as nat), + ranking_pred(0), + create_resp(0), + lift_state(current_state_matches(vrs)) + ); } else if diff > 0 { - assume(false); + assume(false); // Not proven yet!! } else { // diff = 0 // list_resp(diff) ~> current_state_matches(vrs) trivially. @@ -301,9 +392,9 @@ pub proof fn lemma_from_after_recieve_ok_resp_to_send_create_pod_req( &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( - lift_state( + lift_state ( |s: VRSCluster| { - &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } ) @@ -377,9 +468,9 @@ pub proof fn lemma_from_after_recieve_ok_resp_to_send_delete_pod_req( &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( - lift_state( + lift_state ( |s: VRSCluster| { - &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } ) diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index 54bfecc74..f06f21c5a 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -16,8 +16,7 @@ use crate::v_replica_set_controller::model::{reconciler::*}; use crate::v_replica_set_controller::trusted::{ liveness_theorem::*, spec_types::*, step::*, }; -use vstd::math::abs; -use vstd::prelude::*; +use vstd::{prelude::*, math::abs}; verus! { From 0da3edcec7439f861b0a4abb13a84c3568e2e822 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 14 Aug 2024 11:28:33 -0500 Subject: [PATCH 08/22] Refactor proof into lemmas Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 382 +++++++++++------- 1 file changed, 238 insertions(+), 144 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 2b1a9a97c..f217a441f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -42,12 +42,6 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( &&& num_diff_pods_is(vrs, diff)(s) } ); - let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( - |s: VRSCluster| { - &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ); let list_resp = |diff: int| lift_state( |s: VRSCluster| { &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) @@ -57,7 +51,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( // Deal with transition from init to listing the pods. lemma_from_init_step_to_send_list_pods_req(spec, vrs, diff); - lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp(spec, vrs, diff); + lemma_from_after_send_list_pods_req_to_receive_list_pods_resp(spec, vrs, diff); // TODO: Would an invariant be a better way of showing // num_diff_pods_is is maintained across this introductory step? @@ -75,23 +69,6 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( // Now we've listed the pods, perform different behaviors accoding to the difference. if diff < 0 { - // Predicates specific to creating pods. - let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { - &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) - &&& num_diff_pods_is(vrs, diff)(s) - }); - let create_req = |diff: int| lift_state( - |s: VRSCluster| { - &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ); - let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( - |s: VRSCluster| { - &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ); let create_resp = |diff: int| lift_state( |s: VRSCluster| { &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) @@ -99,71 +76,16 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( } ); - assert_by(diff < 0 ==> spec.entails(pre(diff).leads_to(create_resp(diff + 1))), { - // Apply two lemmas relating to the first created pod. - assert forall |resp_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { - lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); - }; - assert forall |req_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { - lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); - }; - - // Attach the two lemmas to the transitivity chain. - leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), create_req(diff)); - leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); - assert_by( - spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), - { - assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) - implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { - let s = ex.head(); - let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - let resp_msg = choose |resp_msg| { - &&& #[trigger] s.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, msg) - &&& resp_msg.content.get_list_response().res.is_Ok() - &&& { - let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() - } - ) - } - }; - assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); - } - ); - assert_by( - spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), - { - assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) - implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); - } - ); - leads_to_trans_n!( - spec, - pre(diff), - list_resp(diff), - tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), - create_req(diff), - tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), - create_resp(diff + 1) - ); - }); + // Add first create pod request after listing pods to leads-to chain. + lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp(spec, vrs, diff); + leads_to_trans_n!( + spec, + pre(diff), + list_resp(diff), + create_resp(diff + 1) + ); - // Is the create request after listing the pods enough? + // Is this enough? if diff + 1 == 0 { // If so, pre(diff) ~> current_state_matches(vrs) trivially. valid_implies_implies_leads_to(spec, create_resp(diff + 1), lift_state(current_state_matches(vrs))); @@ -191,62 +113,16 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( valid_implies_implies_leads_to(spec, ranking_pred(n), create_resp(-n)); }; - // Proving n > 0 => ranking_pred(n) ~> ranking_pred(n - 1) + // Proving n > 0 => ranking_pred(n) ~> ranking_pred(n - 1) assert forall |n: nat| #![trigger ranking_pred(n)] - n > 0 implies spec.entails(ranking_pred(n).leads_to(ranking_pred((n - 1) as nat))) by { + n > 0 implies spec.entails(ranking_pred(n).leads_to(ranking_pred((n - 1) as nat))) by { let diff = -n; - - // Apply two lemmas relating to subsequent created pods. - assert forall |resp_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(create_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { - lemma_from_after_recieve_ok_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); - }; - assert forall |req_msg: VRSMessage| - diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { - lemma_from_after_send_create_pod_req_to_recieve_ok_resp(spec, vrs, req_msg, diff); - }; - - // Chain the lemmas by transitivity. - leads_to_exists_intro(spec, |resp_msg: VRSMessage| create_resp_msg(resp_msg, diff), create_req(diff)); - leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); - assert_by( - spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), - { - assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) - implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { - let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); - } - ); - - // Chain out of and into our ranking predicate. - assert_by( - spec.entails(create_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)))), - { - assert forall |ex| #[trigger] create_resp(diff).satisfied_by(ex) - implies tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)).satisfied_by(ex) by { - let s = ex.head(); - let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); - let resp_msg = choose |resp_msg| { - &&& #[trigger] s.in_flight().contains(resp_msg) - &&& Message::resp_msg_matches_req_msg(resp_msg, msg) - &&& resp_msg.content.get_create_response().res.is_Ok() - }; - assert((|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); - }; - valid_implies_implies_leads_to(spec, create_resp(diff), tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))); - } - ); + lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp(spec, vrs, diff); leads_to_trans_n!( spec, ranking_pred(n), create_resp(diff), - tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)), - create_req(diff), - tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), create_resp(diff + 1), ranking_pred((n - 1) as nat) ); @@ -281,6 +157,224 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( } } +pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff + 1)(s) + } + ) + ) + ), +{ + let list_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_req = |diff: int| lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Apply two lemmas relating to the first created pod. + assert forall |resp_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { + lemma_from_after_receive_list_pods_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { + lemma_from_after_send_create_pod_req_to_receive_ok_resp(spec, vrs, req_msg, diff); + }; + + // Chain lemmas by transitivity. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), create_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); + assert_by( + spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + }; + assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); + } + ); + assert_by( + spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); + } + ); + leads_to_trans_n!( + spec, + list_resp(diff), + tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), + create_req(diff), + tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), + create_resp(diff + 1) + ); +} + +pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + requires + diff < 0, + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff + 1)(s) + } + ) + ) + ), +{ + let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }); + let create_req = |diff: int| lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let create_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Apply two lemmas relating to subsequent created pods. + assert forall |resp_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_resp_msg(resp_msg, diff).leads_to(create_req(diff))) by { + lemma_from_after_receive_ok_resp_to_send_create_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff < 0 implies #[trigger] spec.entails(create_req_msg(req_msg, diff).leads_to(create_resp(diff + 1))) by { + lemma_from_after_send_create_pod_req_to_receive_ok_resp(spec, vrs, req_msg, diff); + }; + + // Chain the lemmas by transitivity. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| create_resp_msg(resp_msg, diff), create_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| create_req_msg(req_msg, diff), create_resp(diff + 1)); + assert_by( + spec.entails(create_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] create_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| create_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_req(diff), tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff))); + } + ); + assert_by( + spec.entails(create_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] create_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + }; + assert((|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, create_resp(diff), tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff))); + } + ); + + leads_to_trans_n!( + spec, + create_resp(diff), + tla_exists(|resp_msg: VRSMessage| create_resp_msg(resp_msg, diff)), + create_req(diff), + tla_exists(|req_msg: VRSMessage| create_req_msg(req_msg, diff)), + create_resp(diff + 1) + ); +} + #[verifier(external_body)] pub proof fn lemma_from_init_step_to_send_list_pods_req( spec: TempPred, vrs: VReplicaSetView, diff: int @@ -305,7 +399,7 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( } #[verifier(external_body)] -pub proof fn lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp( +pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) ensures @@ -329,7 +423,7 @@ pub proof fn lemma_from_after_send_list_pods_req_to_recieve_list_pods_resp( // If there are too few pods, these lemmas apply. #[verifier(external_body)] -pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req( +pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires @@ -354,7 +448,7 @@ pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_create_pod_req( } #[verifier(external_body)] -pub proof fn lemma_from_after_send_create_pod_req_to_recieve_ok_resp( +pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) requires @@ -379,7 +473,7 @@ pub proof fn lemma_from_after_send_create_pod_req_to_recieve_ok_resp( } #[verifier(external_body)] -pub proof fn lemma_from_after_recieve_ok_resp_to_send_create_pod_req( +pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires @@ -405,7 +499,7 @@ pub proof fn lemma_from_after_recieve_ok_resp_to_send_create_pod_req( // If there are too many pods, this lemma applies. #[verifier(external_body)] -pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_delete_pod_req( +pub proof fn lemma_from_after_receive_list_pods_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires @@ -430,7 +524,7 @@ pub proof fn lemma_from_after_recieve_list_pods_resp_to_send_delete_pod_req( } #[verifier(external_body)] -pub proof fn lemma_from_after_send_delete_pod_req_to_recieve_ok_resp( +pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) requires @@ -455,7 +549,7 @@ pub proof fn lemma_from_after_send_delete_pod_req_to_recieve_ok_resp( } #[verifier(external_body)] -pub proof fn lemma_from_after_recieve_ok_resp_to_send_delete_pod_req( +pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires From f66fe2a209440ac3152f53fb08263bf9208508c3 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 14 Aug 2024 14:22:23 -0500 Subject: [PATCH 09/22] Finish too many pods case Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 356 ++++++++++++++++-- 1 file changed, 322 insertions(+), 34 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index f217a441f..d51ef4c82 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -143,7 +143,77 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( lift_state(current_state_matches(vrs)) ); } else if diff > 0 { - assume(false); // Not proven yet!! + let delete_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Add first delete pod request after listing pods to leads-to chain. + lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp(spec, vrs, diff); + leads_to_trans_n!( + spec, + pre(diff), + list_resp(diff), + delete_resp(diff - 1) + ); + + // Is this enough? + if diff - 1 == 0 { + // If so, pre(diff) ~> current_state_matches(vrs) trivially. + valid_implies_implies_leads_to(spec, delete_resp(diff - 1), lift_state(current_state_matches(vrs))); + leads_to_trans_n!( + spec, + pre(diff), + delete_resp(diff - 1), + lift_state(current_state_matches(vrs)) + ); + return; + } + + let ranking_pred = |n: nat| delete_resp(n as int); + + // Useful assertions to let us chain in and out of ranking_pred + assert forall |n: nat| #![trigger ranking_pred(n)] + spec.entails(delete_resp(n as int).leads_to(ranking_pred(n))) by { + valid_implies_implies_leads_to(spec, delete_resp(n as int), ranking_pred(n)); + }; + + assert forall |n: nat| #![trigger ranking_pred(n)] + spec.entails(ranking_pred(n).leads_to(delete_resp(n as int))) by { + valid_implies_implies_leads_to(spec, ranking_pred(n), delete_resp(n as int)); + }; + + // Proving n > 0 => ranking_pred(n) ~> ranking_pred(n - 1) + assert forall |n: nat| #![trigger ranking_pred(n)] + n > 0 implies spec.entails(ranking_pred(n).leads_to(ranking_pred((n - 1) as nat))) by { + let diff = n as int; + lemma_from_after_receive_delete_pod_resp_to_receive_delete_pod_resp(spec, vrs, diff); + + leads_to_trans_n!( + spec, + ranking_pred(n), + delete_resp(diff), + delete_resp(diff - 1), + ranking_pred((n - 1) as nat) + ); + }; + + // Apply ranking function lemma + leads_to_rank_step_one(spec, ranking_pred); + + // Chain everything + valid_implies_implies_leads_to(spec, delete_resp(0), lift_state(current_state_matches(vrs))); + leads_to_trans_n!( + spec, + pre(diff), + delete_resp(diff - 1), + ranking_pred((diff - 1) as nat), + ranking_pred(0), + delete_resp(0), + lift_state(current_state_matches(vrs)) + ); } else { // diff = 0 // list_resp(diff) ~> current_state_matches(vrs) trivially. @@ -157,6 +227,53 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( } } +#[verifier(external_body)] +pub proof fn lemma_from_init_step_to_send_list_pods_req( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) + ) +{ +} + +#[verifier(external_body)] +pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int +) + ensures + spec.entails( + lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ).leads_to( + lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ) + ) + ) +{ +} + +// Lemmas for creating new pods to cover shortfalls. pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) @@ -376,54 +493,57 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp } #[verifier(external_body)] -pub proof fn lemma_from_init_step_to_send_list_pods_req( - spec: TempPred, vrs: VReplicaSetView, diff: int +pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( + spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) + requires + diff < 0, ensures spec.entails( lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( lift_state( |s: VRSCluster| { - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } ) ) - ) + ), { } #[verifier(external_body)] -pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( - spec: TempPred, vrs: VReplicaSetView, diff: int +pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( + spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) + requires + diff < 0, ensures spec.entails( lift_state( |s: VRSCluster| { - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( lift_state( |s: VRSCluster| { - &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) - &&& num_diff_pods_is(vrs, diff)(s) + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff + 1)(s) } ) ) - ) + ), { } -// If there are too few pods, these lemmas apply. #[verifier(external_body)] -pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( +pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires @@ -432,11 +552,11 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( spec.entails( lift_state( |s: VRSCluster| { - &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( - lift_state( + lift_state ( |s: VRSCluster| { &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) @@ -447,57 +567,225 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( { } -#[verifier(external_body)] -pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( - spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int +// Lemmas for deleting excess pods. +pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int ) requires - diff < 0, + diff > 0, ensures spec.entails( lift_state( |s: VRSCluster| { - &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( lift_state( |s: VRSCluster| { - &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) - &&& num_diff_pods_is(vrs, diff + 1)(s) + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff - 1)(s) } ) ) ), { + let list_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_req_msg = |req_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& req_msg_is_the_in_flight_delete_request_at_after_delete_pod_step(vrs, req_msg, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_req = |diff: int| lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Apply two lemmas relating to the first deleted pod. + assert forall |resp_msg: VRSMessage| + diff > 0 implies #[trigger] spec.entails(list_resp_msg(resp_msg, diff).leads_to(delete_req(diff))) by { + lemma_from_after_receive_list_pods_resp_to_send_delete_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff > 0 implies #[trigger] spec.entails(delete_req_msg(req_msg, diff).leads_to(delete_resp(diff - 1))) by { + lemma_from_after_send_delete_pod_req_to_receive_ok_resp(spec, vrs, req_msg, diff); + }; + + // Chain lemmas by transitivity. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| list_resp_msg(resp_msg, diff), delete_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| delete_req_msg(req_msg, diff), delete_resp(diff - 1)); + assert_by( + spec.entails(list_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] list_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs.to_set() == s.resources().values().filter( + |o: DynamicObjectView| { + &&& o.kind == PodView::kind() + &&& o.metadata.namespace.is_Some() + &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + } + ) + } + }; + assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, list_resp(diff), tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))); + } + ); + assert_by( + spec.entails(delete_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] delete_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| delete_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, delete_req(diff), tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff))); + } + ); + leads_to_trans_n!( + spec, + list_resp(diff), + tla_exists(|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff)), + delete_req(diff), + tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)), + delete_resp(diff - 1) + ); } -#[verifier(external_body)] -pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( - spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int +pub proof fn lemma_from_after_receive_delete_pod_resp_to_receive_delete_pod_resp( + spec: TempPred, vrs: VReplicaSetView, diff: int ) requires - diff < 0, + diff > 0, ensures spec.entails( lift_state( |s: VRSCluster| { - &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, diff as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( - lift_state ( + lift_state( |s: VRSCluster| { - &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) - &&& num_diff_pods_is(vrs, diff)(s) + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff - 1)(s) } ) ) ), { + let delete_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { + &&& req_msg_is_the_in_flight_delete_request_at_after_delete_pod_step(vrs, req_msg, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }); + let delete_req = |diff: int| lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (diff - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_resp_msg = |resp_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let delete_resp = |diff: int| lift_state( + |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + + // Apply two lemmas relating to subsequent deleted pods. + assert forall |resp_msg: VRSMessage| + diff > 0 implies #[trigger] spec.entails(delete_resp_msg(resp_msg, diff).leads_to(delete_req(diff))) by { + lemma_from_after_receive_ok_resp_to_send_delete_pod_req(spec, vrs, resp_msg, diff); + }; + assert forall |req_msg: VRSMessage| + diff > 0 implies #[trigger] spec.entails(delete_req_msg(req_msg, diff).leads_to(delete_resp(diff - 1))) by { + lemma_from_after_send_delete_pod_req_to_receive_ok_resp(spec, vrs, req_msg, diff); + }; + + // Chain the lemmas by transitivity. + leads_to_exists_intro(spec, |resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff), delete_req(diff)); + leads_to_exists_intro(spec, |req_msg: VRSMessage| delete_req_msg(req_msg, diff), delete_resp(diff - 1)); + assert_by( + spec.entails(delete_req(diff).leads_to(tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)))), + { + assert forall |ex| #[trigger] delete_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| delete_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, delete_req(diff), tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff))); + } + ); + assert_by( + spec.entails(delete_resp(diff).leads_to(tla_exists(|resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff)))), + { + assert forall |ex| #[trigger] delete_resp(diff).satisfied_by(ex) + implies tla_exists(|resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff)).satisfied_by(ex) by { + let s = ex.head(); + let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + let resp_msg = choose |resp_msg| { + &&& #[trigger] s.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_delete_response().res.is_Ok() + }; + assert((|resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, delete_resp(diff), tla_exists(|resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff))); + } + ); + + leads_to_trans_n!( + spec, + delete_resp(diff), + tla_exists(|resp_msg: VRSMessage| delete_resp_msg(resp_msg, diff)), + delete_req(diff), + tla_exists(|req_msg: VRSMessage| delete_req_msg(req_msg, diff)), + delete_resp(diff - 1) + ); } -// If there are too many pods, this lemma applies. #[verifier(external_body)] pub proof fn lemma_from_after_receive_list_pods_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int @@ -528,7 +816,7 @@ pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) requires - diff < 0, + diff > 0, ensures spec.entails( lift_state( @@ -553,7 +841,7 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires - diff < 0, + diff > 0, ensures spec.entails( lift_state( From 766ceac5bfea958e21e402fb96e4a9b58d4e5ae7 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Thu, 15 Aug 2024 16:59:53 -0500 Subject: [PATCH 10/22] Add from_some_state_to_arbitrary_next_state lemma Signed-off-by: Cody Rivera --- .../proof/controller_runtime_liveness.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/kubernetes_cluster/proof/controller_runtime_liveness.rs b/src/kubernetes_cluster/proof/controller_runtime_liveness.rs index 61036e0a6..5c2d2af38 100644 --- a/src/kubernetes_cluster/proof/controller_runtime_liveness.rs +++ b/src/kubernetes_cluster/proof/controller_runtime_liveness.rs @@ -220,6 +220,35 @@ pub proof fn lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( .leads_to(lift_state(|s: Self| !s.ongoing_reconciles().contains_key(cr.object_ref()))) ), ensures spec.entails(lift_state(Self::at_expected_reconcile_states(cr.object_ref(), state)).leads_to(lift_state(|s: Self| !s.ongoing_reconciles().contains_key(cr.object_ref())))), +{ + Self::lemma_from_some_state_to_arbitrary_next_state(spec, cr, state, next_state); + leads_to_trans_n!( + spec, + lift_state(Self::at_expected_reconcile_states(cr.object_ref(), state)), + lift_state(Self::at_expected_reconcile_states(cr.object_ref(), next_state)), + lift_state(|s: Self| !s.ongoing_reconciles().contains_key(cr.object_ref())) + ); +} + +pub proof fn lemma_from_some_state_to_arbitrary_next_state( + spec: TempPred, cr: K, state: spec_fn(R::T) -> bool, next_state: spec_fn(R::T) -> bool +) + requires + cr.object_ref().kind == K::kind(), + spec.entails(always(lift_action(Self::next()))), + spec.entails(tla_forall(|i| Self::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| Self::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| Self::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(Self::crash_disabled()))), + spec.entails(always(lift_state(Self::busy_disabled()))), + spec.entails(always(lift_state(Self::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(Self::pending_req_of_key_is_unique_with_unique_id(cr.object_ref())))), + spec.entails(always(lift_state(Self::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(cr.object_ref(), state)))), + forall |s| (#[trigger] state(s)) ==> !R::reconcile_error(s) && !R::reconcile_done(s), + forall |cr_1, resp_o, s| + state(s) ==> + #[trigger] next_state(R::reconcile_core(cr_1, resp_o, s).0), + ensures spec.entails(lift_state(Self::at_expected_reconcile_states(cr.object_ref(), state)).leads_to(lift_state(Self::at_expected_reconcile_states(cr.object_ref(), next_state)))), { let at_some_state_and_pending_req_in_flight_or_resp_in_flight = |s: Self| { Self::at_expected_reconcile_states(cr.object_ref(), state)(s) @@ -249,8 +278,7 @@ pub proof fn lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle( spec, lift_state(Self::at_expected_reconcile_states(cr.object_ref(), state)), lift_state(at_some_state_and_pending_req_in_flight_or_resp_in_flight), - lift_state(Self::at_expected_reconcile_states(cr.object_ref(), next_state)), - lift_state(|s: Self| !s.ongoing_reconciles().contains_key(cr.object_ref())) + lift_state(Self::at_expected_reconcile_states(cr.object_ref(), next_state)) ); } From ac805cd0bf9c511067bec5b73aec548586cd10db Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Mon, 19 Aug 2024 11:27:22 -0500 Subject: [PATCH 11/22] Finish termination proof Signed-off-by: Cody Rivera --- .../proof/liveness/terminate.rs | 503 +++++++++++++++++- src/temporal_logic/rules.rs | 33 ++ 2 files changed, 526 insertions(+), 10 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs index 440b9ec40..de3debd08 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/terminate.rs @@ -23,18 +23,501 @@ verus! { pub proof fn reconcile_eventually_terminates(spec: TempPred, vrs: VReplicaSetView) requires - // spec.entails(always(lift_action(VRSCluster::next()))), - // spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), - // spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), - // spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), - // spec.entails(always(lift_state(VRSCluster::crash_disabled()))), - // spec.entails(always(lift_state(VRSCluster::busy_disabled()))), - // spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), - // spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), - // spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::AfterListPods)))), + spec.entails(always(tla_forall(|diff: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(diff)) + ))))), + spec.entails(always(tla_forall(|diff: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(diff)) + ))))), + ensures spec.entails(true_pred().leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), { - assume(false); + assert forall |diff: usize| #![auto] + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(diff)) + )))) by { + always_tla_forall_apply::( + spec, |diff: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(diff)) + )), diff + ); + } + assert forall |diff: usize| #![auto] + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(diff)) + )))) by { + always_tla_forall_apply::( + spec, |diff: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(diff)) + )), diff + ); + } + + let reconcile_idle = |s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }; + + // First, prove that reconcile_done \/ reconcile_error \/ reconcile_ide ~> reconcile_idle. + // Here we simply apply a cluster lemma which uses the wf1 of end_reconcile action. + VRSCluster::lemma_reconcile_error_leads_to_reconcile_idle(spec, vrs.object_ref()); + VRSCluster::lemma_reconcile_done_leads_to_reconcile_idle(spec, vrs.object_ref()); + temp_pred_equality(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)), lift_state(VRSCluster::reconciler_reconcile_done(vrs.object_ref()))); + temp_pred_equality(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)), lift_state(VRSCluster::reconciler_reconcile_error(vrs.object_ref()))); + valid_implies_implies_leads_to(spec, lift_state(reconcile_idle), lift_state(reconcile_idle)); + + // Second, prove that after_create_pod_rank(0) \/ after_delete_pod_rank(0) ~> reconcile_idle. + lemma_from_after_create_or_delete_pod_rank_zero_to_reconcile_idle(spec, vrs); + + // Third, prove for all n that AfterCreatePod(n) ~> reconcile_idle. + assert forall |n: usize| #![auto] + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))) + .leads_to(lift_state(reconcile_idle))) by { + assert forall |n: usize| #![trigger after_create_pod_rank(vrs, n)] + n > 0 implies spec.entails(lift_state(after_create_pod_rank(vrs, n)) + .leads_to(lift_state(after_create_pod_rank(vrs, (n - 1) as usize)))) by { + lemma_from_after_create_pod_rank_n_to_create_pod_rank_n_minus_1(spec, vrs, n); + } + leads_to_rank_step_one_usize(spec, |n| lift_state(after_create_pod_rank(vrs, n))); + + implies_to_leads_to( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(after_create_pod_rank(vrs, n)) + ); + assert(spec.entails((|n| lift_state(after_create_pod_rank(vrs, n)))(n) + .leads_to((|n| lift_state(after_create_pod_rank(vrs, n)))(0)))); + leads_to_trans_n!( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(after_create_pod_rank(vrs, n)), + lift_state(after_create_pod_rank(vrs, 0)), + lift_state(reconcile_idle) + ); + } + + // Similarly prove for all n that AfterDeletePod(n) ~> reconcile_idle. + assert forall |n: usize| #![auto] + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))) + .leads_to(lift_state(reconcile_idle))) by { + assert forall |n: usize| #![trigger after_delete_pod_rank(vrs, n)] + n > 0 implies spec.entails(lift_state(after_delete_pod_rank(vrs, n)) + .leads_to(lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)))) by { + lemma_from_after_delete_pod_rank_n_to_delete_pod_rank_n_minus_1(spec, vrs, n); + } + leads_to_rank_step_one_usize(spec, |n| lift_state(after_delete_pod_rank(vrs, n))); + + implies_to_leads_to( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(after_delete_pod_rank(vrs, n)) + ); + assert(spec.entails((|n| lift_state(after_delete_pod_rank(vrs, n)))(n) + .leads_to((|n| lift_state(after_delete_pod_rank(vrs, n)))(0)))); + leads_to_trans_n!( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(after_delete_pod_rank(vrs, n)), + lift_state(after_delete_pod_rank(vrs, 0)), + lift_state(reconcile_idle) + ); + } + + // Fourth, prove that after_list_pods ~> reconcile_idle. + lemma_from_after_list_pods_to_reconcile_idle(spec, vrs); + + // Fifth, prove that reconcile init state can reach AfterListPods. + VRSCluster::lemma_from_init_state_to_next_state_to_reconcile_idle( + spec, vrs, at_step_closure(VReplicaSetReconcileStep::Init), at_step_closure(VReplicaSetReconcileStep::AfterListPods)); + + // Finally, combine all cases + leads_to_exists_intro( + spec, + |n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(reconcile_idle) + ); + leads_to_exists_intro( + spec, + |n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(reconcile_idle) + ); + let at_after_create_pod = |n: usize| { + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))) + }; + let at_after_delete_pod = |n: usize| { + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))) + }; + + lemma_true_equal_to_reconcile_idle_or_at_any_state(vrs); + + or_leads_to_combine_and_equality!( + spec, + true_pred(), + lift_state(reconcile_idle), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Init)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods)), + tla_exists(at_after_create_pod), + tla_exists(at_after_delete_pod), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(reconcile_idle) + ); +} + +pub open spec fn at_step_state_pred(vrs: VReplicaSetView, step: VReplicaSetReconcileStep) -> StatePred { + VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == step) +} + +pub open spec fn after_create_pod_rank(vrs: VReplicaSetView, diff: usize) -> StatePred { + |s: VRSCluster| { + ||| at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(diff))(s) + // There may have been an error as well. + ||| at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)(s) + } +} + +pub open spec fn after_delete_pod_rank(vrs: VReplicaSetView, diff: usize) -> StatePred { + |s: VRSCluster| { + ||| at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff))(s) + // There may have been an error as well. + ||| at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)(s) + } +} + +proof fn lemma_from_after_create_or_delete_pod_rank_zero_to_reconcile_idle( + spec: TempPred, vrs: VReplicaSetView +) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + // Make sure there is a message in flight, so we can progress to the next state. + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(0)))))), + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(0)))))), + // The next state will lead to reconcile_idle. + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + ensures + spec.entails(lift_state(after_create_pod_rank(vrs, 0)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + spec.entails(lift_state(after_delete_pod_rank(vrs, 0)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), +{ + let state_after_create_or_delete = |s: VReplicaSetReconcileState| { + s.reconcile_step == VReplicaSetReconcileStep::Done + || s.reconcile_step == VReplicaSetReconcileStep::Error + }; + or_leads_to_combine_and_equality!( + spec, lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_create_or_delete)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + ); + + VRSCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, vrs, at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(0)), state_after_create_or_delete); + VRSCluster::lemma_from_some_state_to_arbitrary_next_state_to_reconcile_idle(spec, vrs, at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(0)), state_after_create_or_delete); + + or_leads_to_combine_and_equality!( + spec, lift_state(after_create_pod_rank(vrs, 0)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(0))), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + ); + or_leads_to_combine_and_equality!( + spec, lift_state(after_delete_pod_rank(vrs, 0)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(0))), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + ); +} + +proof fn lemma_from_after_create_pod_rank_n_to_create_pod_rank_n_minus_1( + spec: TempPred, vrs: VReplicaSetView, n: usize +) + requires + n > 0, + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + // Make sure there is a message in flight, so we can progress to the next state. + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(n)))))), + ensures + spec.entails(lift_state(after_create_pod_rank(vrs, n)) + .leads_to(lift_state(after_create_pod_rank(vrs, (n - 1) as usize)))), +{ + let state_after_create = |s: VReplicaSetReconcileState| { + s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod((n - 1) as usize) + || s.reconcile_step == VReplicaSetReconcileStep::Error + }; + + valid_implies_implies_leads_to( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)), + lift_state(after_create_pod_rank(vrs, (n - 1) as usize)) + ); + + VRSCluster::lemma_from_some_state_to_arbitrary_next_state(spec, vrs, at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(n)), state_after_create); + + implies_to_leads_to( + spec, + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_create)), + lift_state(after_create_pod_rank(vrs, (n - 1) as usize)) + ); + leads_to_trans_n!( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_create)), + lift_state(after_create_pod_rank(vrs, (n - 1) as usize)) + ); + + or_leads_to_combine_and_equality!( + spec, lift_state(after_create_pod_rank(vrs, n)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(after_create_pod_rank(vrs, (n - 1) as usize)) + ); +} + +proof fn lemma_from_after_delete_pod_rank_n_to_delete_pod_rank_n_minus_1( + spec: TempPred, vrs: VReplicaSetView, n: usize +) + requires + n > 0, + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + // Make sure there is a message in flight, so we can progress to the next state. + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(n)))))), + ensures + spec.entails(lift_state(after_delete_pod_rank(vrs, n)) + .leads_to(lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)))), +{ + let state_after_delete = |s: VReplicaSetReconcileState| { + s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod((n - 1) as usize) + || s.reconcile_step == VReplicaSetReconcileStep::Error + }; + + valid_implies_implies_leads_to( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)), + lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)) + ); + + VRSCluster::lemma_from_some_state_to_arbitrary_next_state(spec, vrs, at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(n)), state_after_delete); + + implies_to_leads_to( + spec, + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_delete)), + lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)) + ); + leads_to_trans_n!( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_delete)), + lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)) + ); + + or_leads_to_combine_and_equality!( + spec, lift_state(after_delete_pod_rank(vrs, n)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(after_delete_pod_rank(vrs, (n - 1) as usize)) + ); +} + +proof fn lemma_from_after_list_pods_to_reconcile_idle( + spec: TempPred, vrs: VReplicaSetView +) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::external_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + // Make sure there is a message in flight, so we can progress to the next state. + spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::AfterListPods)))), + // The next state will lead to reconcile_idle. + forall |n: usize| #![auto] + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + forall |n: usize| #![auto] + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), + ensures + spec.entails(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods)) + .leads_to(lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())))), +{ + let state_after_list_pods = |s: VReplicaSetReconcileState| { + ||| exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod(n) + ||| exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod(n) + ||| s.reconcile_step == VReplicaSetReconcileStep::Done + ||| s.reconcile_step == VReplicaSetReconcileStep::Error + }; + leads_to_exists_intro( + spec, + |n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))), + lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())) + ); + leads_to_exists_intro( + spec, + |n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))), + lift_state(|s: VRSCluster| !s.ongoing_reconciles().contains_key(vrs.object_ref())) + ); + let at_after_create_pod = |n: usize| { + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))) + }; + let at_after_delete_pod = |n: usize| { + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))) + }; + + assert_by( + tla_exists(at_after_create_pod) == + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod(n))), + { + assert forall |ex| #![auto] + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod(n))).satisfied_by(ex) implies + tla_exists(at_after_create_pod).satisfied_by(ex) by { + let s = ex.head().ongoing_reconciles()[vrs.object_ref()].local_state; + let witness_n = choose |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod(n); + assert(at_after_create_pod(witness_n).satisfied_by(ex)); + } + + temp_pred_equality( + tla_exists(at_after_create_pod), + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterCreatePod(n))) + ); + } + ); + assert_by( + tla_exists(at_after_delete_pod) == + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod(n))), + { + assert forall |ex| #![auto] + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod(n))).satisfied_by(ex) implies + tla_exists(at_after_delete_pod).satisfied_by(ex) by { + let s = ex.head().ongoing_reconciles()[vrs.object_ref()].local_state; + let witness_n = choose |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod(n); + assert(at_after_delete_pod(witness_n).satisfied_by(ex)); + } + + temp_pred_equality( + tla_exists(at_after_delete_pod), + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), |s: VReplicaSetReconcileState| + exists |n: usize| s.reconcile_step == VReplicaSetReconcileStep::AfterDeletePod(n))) + ); + } + ); + or_leads_to_combine_and_equality!( + spec, lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_list_pods)), + tla_exists(at_after_create_pod), + tla_exists(at_after_delete_pod), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done)), + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error)); + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + ); + + + VRSCluster::lemma_from_some_state_to_arbitrary_next_state(spec, vrs, at_step_closure(VReplicaSetReconcileStep::AfterListPods), state_after_list_pods); + + leads_to_trans_n!( + spec, + lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods)), + lift_state(VRSCluster::at_expected_reconcile_states(vrs.object_ref(), state_after_list_pods)), + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + ); +} + +proof fn lemma_true_equal_to_reconcile_idle_or_at_any_state(vrs: VReplicaSetView) + ensures true_pred::() + == lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Init))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error))) +{ + let rhs = lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Init))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error))); + + assert forall |ex| #![auto] true_pred::().satisfied_by(ex) implies rhs.satisfied_by(ex) by { + let s = ex.head(); + if s.ongoing_reconciles().contains_key(vrs.object_ref()) { + let step = s.ongoing_reconciles()[vrs.object_ref()].local_state.reconcile_step; + match step { + VReplicaSetReconcileStep::AfterCreatePod(n) => { + // Introduce tla_exists with n as witness. + assert((|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))))(n).satisfied_by(ex)); + assert(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n)))).satisfied_by(ex)); + }, + VReplicaSetReconcileStep::AfterDeletePod(n) => { + // Introduce tla_exists with n as witness. + assert((|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))))(n).satisfied_by(ex)); + assert(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n)))).satisfied_by(ex)); + }, + step => {}, + } + } + } + + temp_pred_equality( + true_pred::(), + lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Init))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterListPods))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterCreatePod(n))))) + .or(tla_exists(|n| lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::AfterDeletePod(n))))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Done))) + .or(lift_state(at_step_state_pred(vrs, VReplicaSetReconcileStep::Error))) + ); } } diff --git a/src/temporal_logic/rules.rs b/src/temporal_logic/rules.rs index c21e848ff..44d2c3067 100644 --- a/src/temporal_logic/rules.rs +++ b/src/temporal_logic/rules.rs @@ -1972,4 +1972,37 @@ proof fn leads_to_rank_step_one_help(spec: TempPred, p: spec_fn(nat) -> Te } } +// usize version of the proof rule leads_to_rank_step_one. +pub proof fn leads_to_rank_step_one_usize(spec: TempPred, p: spec_fn(usize) -> TempPred) + requires + forall |n: usize| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as usize)))), + ensures + forall |n: usize| #[trigger] spec.entails(p(n).leads_to(p(0))), +{ + let pre = { + forall |n: usize| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as usize)))) + }; + assert forall |n: usize| pre implies #[trigger] spec.entails(p(n).leads_to(p(0))) by { + leads_to_rank_step_one_usize_help(spec, p, n); + } +} + +proof fn leads_to_rank_step_one_usize_help(spec: TempPred, p: spec_fn(usize) -> TempPred, n: usize) + requires + forall |n: usize| #![trigger p(n)] (n > 0 ==> spec.entails(p(n).leads_to(p((n - 1) as usize)))), + ensures + spec.entails(p(n).leads_to(p(0))), + decreases n, +{ + if n > 0 { + // p(n) ~> p(n - 1), p(n - 1) ~> p(0) + // combine with leads-to transitivity + leads_to_rank_step_one_usize_help(spec, p, (n - 1) as usize); + leads_to_trans_n!(spec, p(n), p((n - 1) as usize), p(0)); + } else { + // p(0) ~> p(0) trivially + leads_to_self_temp(p(0)); + } +} + } From f72f0cdc88d82c811234270da4fb072ac6ccb334 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Mon, 26 Aug 2024 14:51:21 -0500 Subject: [PATCH 12/22] Get everything modulo boundary lemmas verified Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 4 + .../proof/liveness/proof.rs | 134 ++++++++++++++++-- .../proof/liveness/resource_match.rs | 12 ++ .../proof/liveness/spec.rs | 73 ++++++++-- .../proof/predicate.rs | 8 +- .../trusted/liveness_theorem.rs | 1 - 6 files changed, 208 insertions(+), 24 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 2cc3febb2..491824922 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -24,4 +24,8 @@ use vstd::{multiset::*, prelude::*, string::*}; verus! { +pub open spec fn cluster_resources_is_finite() -> StatePred { + |s: VRSCluster| s.resources().dom().finite() +} + } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs index 0a97d5ba6..d6b71edd5 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs @@ -20,6 +20,7 @@ use crate::v_replica_set_controller::{ helper_invariants, liveness::{ spec::*, + resource_match::*, terminate, }, predicate::*, @@ -81,18 +82,135 @@ proof fn lemma_true_leads_to_always_current_state_matches(vrs: VReplicaSetView) { let spec = assumption_and_invariants_of_all_phases(vrs); - assert forall |k : usize| #![auto] spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k)))))) by { - always_tla_forall_apply(spec, |k: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k)))), k); - } - assert forall |k : usize| #![auto] spec.entails(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k)))))) by { - always_tla_forall_apply(spec, |k: usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k)))), k); - } - + let reconcile_idle = lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }); + let reconcile_scheduled = lift_state(|s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.scheduled_reconciles().contains_key(vrs.object_ref()) + }); + let at_init = lift_state(no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)); + let diff_at_init = |diff| lift_state( + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + // The use of termination property ensures spec |= true ~> reconcile_idle. terminate::reconcile_eventually_terminates(spec, vrs); // Then we can continue to show that spec |= reconcile_idle ~> []current_state_matches(vrs). - assume(false); + // The following two lemmas show that spec |= reconcile_idle ~> init /\ no_pending_req. + lemma_from_reconcile_idle_to_scheduled(spec, vrs); + lemma_from_scheduled_to_init_step(spec, vrs); + + // Show that true == exists |diff| num_diff_pods_is(diff). + assert_by(true_pred::() == tla_exists(|diff| lift_state(num_diff_pods_is(vrs, diff))), { + let exists_num_diff_pods_is = tla_exists(|diff| lift_state(num_diff_pods_is(vrs, diff))); + assert forall |ex: Execution| + true_pred::().satisfied_by(ex) implies #[trigger] exists_num_diff_pods_is.satisfied_by(ex) by { + let s = ex.head(); + let pods = matching_pods(vrs, s.resources()); + let diff = pods.len() - vrs.spec.replicas.unwrap_or(0); + + // Instantiate exists statement. + assert((|diff| lift_state(num_diff_pods_is(vrs, diff)))(diff).satisfied_by(ex)); + } + assert(valid(true_pred::().implies(exists_num_diff_pods_is))); + temp_pred_equality(true_pred::(), tla_exists(|diff| lift_state(num_diff_pods_is(vrs, diff)))); + }); + + // Show that init /\ no_pending_req ~> exists |diff| (num_diff_pods_is(diff) /\ init) + assert_by(spec.entails(at_init.leads_to(tla_exists(diff_at_init))), { + assert forall |ex: Execution| + at_init.satisfied_by(ex) implies #[trigger] tla_exists(diff_at_init).satisfied_by(ex) by { + assert(tla_exists(|diff| lift_state(num_diff_pods_is(vrs, diff))).satisfied_by(ex)); + let diff = choose |diff| lift_state(#[trigger] num_diff_pods_is(vrs, diff)).satisfied_by(ex); + assert(diff_at_init(diff).satisfied_by(ex)); + } + implies_to_leads_to(spec, at_init, tla_exists(diff_at_init)); + }); + + // The following shows exists |diff| (num_diff_pods_is(diff) /\ init) ~> current_state_matches(vrs) + assert forall |diff| #[trigger] spec.entails(diff_at_init(diff).leads_to(lift_state(current_state_matches(vrs)))) by { + lemma_from_diff_and_init_to_current_state_matches(spec, vrs, diff); + } + leads_to_exists_intro(spec, diff_at_init, lift_state(current_state_matches(vrs))); + + // Chain everything together + leads_to_trans_n!( + spec, + true_pred(), + reconcile_idle, + reconcile_scheduled, + at_init, + tla_exists(diff_at_init), + lift_state(current_state_matches(vrs)) + ); + + // Further prove stability + lemma_current_state_matches_is_stable(spec, vrs, true_pred()); +} + +proof fn lemma_from_reconcile_idle_to_scheduled(spec: TempPred, vrs: VReplicaSetView) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::schedule_controller_reconcile().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::desired_state_is(vrs)))), + ensures + spec.entails(lift_state(|s: VRSCluster| { !s.ongoing_reconciles().contains_key(vrs.object_ref()) }) + .leads_to(lift_state(|s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.scheduled_reconciles().contains_key(vrs.object_ref()) + }))), +{ + let pre = |s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& !s.scheduled_reconciles().contains_key(vrs.object_ref()) + }; + let post = |s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.scheduled_reconciles().contains_key(vrs.object_ref()) + }; + let input = vrs.object_ref(); + VRSCluster::lemma_pre_leads_to_post_by_schedule_controller_reconcile_borrow_from_spec(spec, input, VRSCluster::next(), VRSCluster::desired_state_is(vrs), pre, post); + valid_implies_implies_leads_to(spec, lift_state(post), lift_state(post)); + or_leads_to_combine_temp(spec, lift_state(pre), lift_state(post), lift_state(post)); + temp_pred_equality(lift_state(pre).or(lift_state(post)), lift_state(|s: VRSCluster| {!s.ongoing_reconciles().contains_key(vrs.object_ref())})); +} + +proof fn lemma_from_scheduled_to_init_step(spec: TempPred, vrs: VReplicaSetView) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::the_object_in_schedule_has_spec_and_uid_as(vrs)))), + ensures + spec.entails(lift_state(|s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.scheduled_reconciles().contains_key(vrs.object_ref()) + }).leads_to(lift_state(no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)))), +{ + let pre = |s: VRSCluster| { + &&& !s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& s.scheduled_reconciles().contains_key(vrs.object_ref()) + }; + let post = no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init); + let input = (None, Some(vrs.object_ref())); + let stronger_next = |s, s_prime| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()(s) + &&& VRSCluster::the_object_in_schedule_has_spec_and_uid_as(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()), + lift_state(VRSCluster::the_object_in_schedule_has_spec_and_uid_as(vrs)) + ); + VRSCluster::lemma_pre_leads_to_post_by_controller(spec, input, stronger_next, VRSCluster::run_scheduled_reconcile(), pre, post); } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index d51ef4c82..a087ace5e 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -861,4 +861,16 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( { } +// Ensures that once we've reconciled, we stay reconciled. +#[verifier(external_body)] +pub proof fn lemma_current_state_matches_is_stable( + spec: TempPred, vrs: VReplicaSetView, p: TempPred +) + requires + spec.entails(p.leads_to(lift_state(current_state_matches(vrs)))), + ensures + spec.entails(p.leads_to(always(lift_state(current_state_matches(vrs))))), +{ +} + } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 562789af6..18fac1b37 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -26,32 +26,32 @@ verus! { pub open spec fn assumption_and_invariants_of_all_phases(vrs: VReplicaSetView) -> TempPred { invariants(vrs) .and(always(lift_state(desired_state_is(vrs)))) - .and(true_pred() /* invariants_since_phase_i(vrs) */) - .and(true_pred() /* invariants_since_phase_ii(vrs) */) - .and(true_pred() /* invariants_since_phase_iii(vrs) */) - .and(true_pred() /* invariants_since_phase_iv(vrs) */) - .and(true_pred() /* invariants_since_phase_v(vrs) */) - .and(true_pred() /* invariants_since_phase_vi(vrs) */) - .and(true_pred() /* invariants_since_phase_vii(vrs) */) + .and(invariants_since_phase_i(vrs)) + .and(invariants_since_phase_ii(vrs)) + .and(invariants_since_phase_iii(vrs)) + .and(invariants_since_phase_iv(vrs)) + .and(invariants_since_phase_v(vrs)) + .and(invariants_since_phase_vi(vrs)) + .and(invariants_since_phase_vii(vrs)) } pub open spec fn invariants_since_phase_n(n: nat, vrs: VReplicaSetView) -> TempPred { if n == 0 { invariants(vrs).and(always(lift_state(desired_state_is(vrs)))) } else if n == 1 { - true_pred() // invariants_since_phase_i(vrs) + invariants_since_phase_i(vrs) } else if n == 2 { - true_pred() // invariants_since_phase_ii(vrs) + invariants_since_phase_ii(vrs) } else if n == 3 { - true_pred() // invariants_since_phase_iii(vrs) + invariants_since_phase_iii(vrs) } else if n == 4 { - true_pred() // invariants_since_phase_iv(vrs) + invariants_since_phase_iv(vrs) } else if n == 5 { - true_pred() // invariants_since_phase_v(vrs) + invariants_since_phase_v(vrs) } else if n == 6 { - true_pred() // invariants_since_phase_vi(vrs) + invariants_since_phase_vi(vrs) } else if n == 7 { - true_pred() // invariants_since_phase_vii(vrs) + invariants_since_phase_vii(vrs) } else { true_pred() } @@ -103,8 +103,53 @@ pub open spec fn invariants(vrs: VReplicaSetView) -> TempPred { pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> TempPred { always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id())) .and(always(lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterListPods))))) + .and(always(lift_state(VRSCluster::every_in_flight_req_msg_has_different_id_from_pending_req_msg_of(vrs.object_ref())))) + .and(always(lift_state(VRSCluster::object_in_ok_get_response_has_smaller_rv_than_etcd()))) + .and(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) + .and(always(lift_state(VRSCluster::every_in_flight_msg_has_lower_id_than_allocator()))) + .and(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) + .and(always(lift_state(VRSCluster::each_scheduled_object_has_consistent_key_and_valid_metadata()))) + .and(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) + .and(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) + .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) +} + +/// The first notable phase comes when crash and k8s busy are always disabled and the object in schedule always has the same +/// spec and uid as the cr we provide. +/// +/// Note that don't try to find any connections between those invariants -- they are put together because they don't have to +/// wait for another of them to first be satisfied. +pub open spec fn invariants_since_phase_i(vrs: VReplicaSetView) -> TempPred { + always(lift_state(VRSCluster::crash_disabled())) + .and(always(lift_state(VRSCluster::busy_disabled()))) + .and(always(lift_state(VRSCluster::the_object_in_schedule_has_spec_and_uid_as(vrs)))) +} + +// Placeholder invariants -- will develop from the WF1 lemmas. +pub open spec fn invariants_since_phase_ii(vrs: VReplicaSetView) -> TempPred { + true_pred() +} + +pub open spec fn invariants_since_phase_iii(vrs: VReplicaSetView) -> TempPred { + true_pred() +} + +pub open spec fn invariants_since_phase_iv(vrs: VReplicaSetView) -> TempPred { + true_pred() +} + +pub open spec fn invariants_since_phase_v(vrs: VReplicaSetView) -> TempPred { + true_pred() +} + +pub open spec fn invariants_since_phase_vi(vrs: VReplicaSetView) -> TempPred { + true_pred() +} + +pub open spec fn invariants_since_phase_vii(vrs: VReplicaSetView) -> TempPred { + true_pred() } pub proof fn sm_spec_entails_all_invariants(vrs: VReplicaSetView) diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index f06f21c5a..48dc5a8e7 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -35,6 +35,13 @@ pub open spec fn at_vrs_step_with_vrs(vrs: VReplicaSetView, step: VReplicaSetRec } } +pub open spec fn no_pending_req_at_vrs_step_with_vrs(vrs: VReplicaSetView, step: VReplicaSetReconcileStep) -> StatePred { + |s: VRSCluster| { + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::no_pending_req_msg(s, vrs.object_ref()) + } +} + // Predicates for reasoning about pods pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> Set { Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)) @@ -43,7 +50,6 @@ pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> pub open spec fn num_diff_pods_is(vrs: VReplicaSetView, diff: int) -> StatePred { |s: VRSCluster| { let pods = matching_pods(vrs, s.resources()); - &&& pods.finite() &&& pods.len() - vrs.spec.replicas.unwrap_or(0) == diff } } diff --git a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs index 9fa853063..c19e054ff 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs @@ -28,7 +28,6 @@ pub open spec fn current_state_matches(vrs: VReplicaSetView) -> StatePred bool { let pods: Set = Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)); - &&& pods.finite() &&& pods.len() == vrs.spec.replicas.unwrap_or(0) } From 30754a24106bf900a1ac7315a4bb2f1c0809e0c6 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 28 Aug 2024 13:08:49 -0500 Subject: [PATCH 13/22] Prove WF1 lemma: from init to list pods Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 75 +++++++++++++++- .../proof/liveness/api_actions.rs | 79 ++++++++++++++++ .../proof/liveness/mod.rs | 1 + .../proof/liveness/proof.rs | 2 +- .../proof/liveness/resource_match.rs | 90 +++++++++++++++++-- .../proof/liveness/spec.rs | 4 + .../proof/predicate.rs | 19 +++- .../trusted/liveness_theorem.rs | 10 ++- 8 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 491824922..f077175b9 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -3,7 +3,7 @@ #![allow(unused_imports)] use crate::external_api::spec::{EmptyAPI, EmptyTypeView}; use crate::kubernetes_api_objects::spec::{ - api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, + api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, prelude::*, resource::*, stateful_set::*, }; use crate::kubernetes_cluster::spec::{ @@ -18,7 +18,7 @@ use crate::vstd_ext::{multiset_lib, seq_lib, string_view::*}; use crate::v_replica_set_controller::{ model::reconciler::*, proof::{predicate::*}, - trusted::{spec_types::*, step::*}, + trusted::{spec_types::*, step::*, liveness_theorem::*}, }; use vstd::{multiset::*, prelude::*, string::*}; @@ -28,4 +28,75 @@ pub open spec fn cluster_resources_is_finite() -> StatePred { |s: VRSCluster| s.resources().dom().finite() } +pub open spec fn every_create_request_has_an_empty_deletion_timestamp() -> StatePred { + |s: VRSCluster| { + forall |msg: VRSMessage| #![trigger msg.dst.is_ApiServer(), msg.content.is_APIRequest()] { + let content = msg.content; + let obj = content.get_create_request().obj; + &&& s.in_flight().contains(msg) + &&& msg.dst.is_ApiServer() + &&& msg.content.is_APIRequest() + &&& content.is_create_request() + } ==> { + let content = msg.content; + let obj = content.get_create_request().obj; + &&& obj.metadata.deletion_timestamp.is_None() + } + } +} + +pub open spec fn no_pending_update_or_update_status_request_on_pods() -> StatePred { + |s: VRSCluster| { + forall |msg: VRSMessage| { + &&& s.in_flight().contains(msg) + &&& #[trigger] msg.dst.is_ApiServer() + &&& #[trigger] msg.content.is_APIRequest() + } ==> { + &&& msg.content.is_update_request() ==> msg.content.get_update_request().key().kind != PodView::kind() + &&& msg.content.is_update_status_request() ==> msg.content.get_update_status_request().key().kind != PodView::kind() + } + } +} + + +pub open spec fn every_create_matching_pod_request_implies_at_after_create_pod_step( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + forall |msg: VRSMessage| #![trigger msg.dst.is_ApiServer(), msg.content.is_APIRequest()] { + let content = msg.content; + let obj = content.get_create_request().obj; + &&& s.in_flight().contains(msg) + &&& msg.dst.is_ApiServer() + &&& msg.content.is_APIRequest() + &&& content.is_create_request() + &&& owned_selector_match_is(vrs, obj) + } ==> { + &&& exists |diff: usize| #[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterCreatePod(diff))(s) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) + } + } +} + +pub open spec fn every_delete_matching_pod_request_implies_at_after_delete_pod_step( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + forall |msg: VRSMessage| #![trigger msg.dst.is_ApiServer(), msg.content.is_APIRequest()] { + let content = msg.content; + let key = content.get_delete_request().key; + let obj = s.resources()[key]; + &&& s.in_flight().contains(msg) + &&& msg.dst.is_ApiServer() + &&& msg.content.is_APIRequest() + &&& content.is_delete_request() + &&& s.resources().contains_key(key) + &&& owned_selector_match_is(vrs, obj) + } ==> { + &&& exists |diff: usize| #[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff))(s) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) + } + } +} + } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs new file mode 100644 index 000000000..0ff1e1282 --- /dev/null +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -0,0 +1,79 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::spec::{ + api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, +}; +use crate::kubernetes_cluster::spec::{ + api_server::state_machine::*, + builtin_controllers::types::BuiltinControllerChoice, + cluster::*, + cluster_state_machine::Step, + controller::types::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use crate::vstd_ext::{map_lib::*, string_view::*}; +use crate::v_replica_set_controller::{ + model::{reconciler::*}, + proof::{helper_invariants, predicate::*}, + trusted::{spec_types::*, step::*, liveness_theorem::*}, +}; +use vstd::{prelude::*, string::*, map::*, map_lib::*, math::abs}; + +verus! { + +pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s: VRSCluster, s_prime: VRSCluster, vrs: VReplicaSetView, diff: int, msg: VRSMessage, +) + requires + VRSCluster::next_step(s, s_prime, Step::ApiServerStep(Some(msg))), + VRSCluster::crash_disabled()(s), + VRSCluster::busy_disabled()(s), + VRSCluster::every_in_flight_msg_has_unique_id()(s), + VRSCluster::each_object_in_etcd_is_well_formed()(s), + helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s), + helper_invariants::no_pending_update_or_update_status_request_on_pods()(s), + helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s), + helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s), + forall |diff: usize| !(#[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterCreatePod(diff))(s)), + forall |diff: usize| !(#[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff))(s)), + ensures + matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources()), +{ + // Dispatch through all the requests which may mutate the k-v store. + let mutates_key = if msg.content.is_create_request() { + let req = msg.content.get_create_request(); + Some(ObjectRef{ + kind: req.obj.kind, + name: if req.obj.metadata.name.is_Some() { + req.obj.metadata.name.unwrap() + } else { + generate_name(s.kubernetes_api_state) + }, + namespace: req.namespace, + }) + } else if msg.content.is_delete_request() { + let req = msg.content.get_delete_request(); + Some(req.key) + } else if msg.content.is_update_request() { + let req = msg.content.get_update_request(); + Some(req.key()) + } else if msg.content.is_update_status_request() { + let req = msg.content.get_update_status_request(); + Some(req.key()) + } else { + None + }; + + match mutates_key { + Some(key) => { + assert_maps_equal!(s.resources().remove(key) == s_prime.resources().remove(key)); + assert_maps_equal!(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + _ => {} + }; +} + +} diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs index 5af1a566f..a5ecfe8c7 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/mod.rs @@ -1,5 +1,6 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: MIT +pub mod api_actions; pub mod proof; pub mod resource_match; pub mod spec; diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs index d6b71edd5..c6d2f881b 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs @@ -90,7 +90,7 @@ proof fn lemma_true_leads_to_always_current_state_matches(vrs: VReplicaSetView) let at_init = lift_state(no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)); let diff_at_init = |diff| lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) &&& num_diff_pods_is(vrs, diff)(s) } ); diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index a087ace5e..3980bd113 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -16,7 +16,7 @@ use crate::temporal_logic::{defs::*, rules::*}; use crate::vstd_ext::{map_lib::*, string_view::*}; use crate::v_replica_set_controller::{ model::{reconciler::*}, - proof::{helper_invariants, predicate::*}, + proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; use vstd::{prelude::*, string::*, math::abs}; @@ -26,11 +26,24 @@ verus! { pub proof fn lemma_from_diff_and_init_to_current_state_matches( spec: TempPred, vrs: VReplicaSetView, diff: int, ) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), ensures spec.entails( lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to(lift_state(current_state_matches(vrs))) @@ -38,7 +51,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( { let pre = |diff: int| lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) &&& num_diff_pods_is(vrs, diff)(s) } ); @@ -227,15 +240,27 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( } } -#[verifier(external_body)] pub proof fn lemma_from_init_step_to_send_list_pods_req( spec: TempPred, vrs: VReplicaSetView, diff: int ) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), ensures spec.entails( lift_state( |s: VRSCluster| { - &&& at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( @@ -248,6 +273,61 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( ) ) { + let pre = |s: VRSCluster| { + &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = (None, Some(vrs.object_ref())); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), + |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)(s) + &&& helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + let state_is_init = |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), state_is_init)), + lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(msg) => { + let msg = msg.unwrap(); + lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s, s_prime, vrs, diff, msg + ); + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + _ => {} + } + } + + VRSCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, VRSCluster::continue_reconcile(), pre, post + ); } #[verifier(external_body)] diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 18fac1b37..3b3150e6e 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -114,6 +114,10 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + .and(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))) + .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) + .and(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) + .and(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) } /// The first notable phase comes when crash and k8s busy are always disabled and the object in schedule always has the same diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index 48dc5a8e7..c1d16d0ed 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -44,7 +44,24 @@ pub open spec fn no_pending_req_at_vrs_step_with_vrs(vrs: VReplicaSetView, step: // Predicates for reasoning about pods pub open spec fn matching_pods(vrs: VReplicaSetView, resources: StoredState) -> Set { - Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)) + Set::new(|key: ObjectRef| { + let obj = resources[key]; + &&& resources.contains_key(key) + &&& owned_selector_match_is(vrs, obj) + }) +} + +pub open spec fn matching_pod_entries(vrs: VReplicaSetView, resources: StoredState) -> Map { + Map::new( + |key: ObjectRef| { + let obj = resources[key]; + &&& resources.contains_key(key) + &&& owned_selector_match_is(vrs, obj) + }, + |key: ObjectRef| { + resources[key] + }, + ) } pub open spec fn num_diff_pods_is(vrs: VReplicaSetView, diff: int) -> StatePred { diff --git a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs index c19e054ff..eaeb4ce58 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs @@ -27,13 +27,15 @@ pub open spec fn current_state_matches(vrs: VReplicaSetView) -> StatePred bool { - let pods: Set = Set::new(|k: ObjectRef| owned_selector_match_is(vrs, resources, k)); + let pods: Set = Set::new(|key: ObjectRef| { + let obj = resources[key]; + &&& resources.contains_key(key) + &&& owned_selector_match_is(vrs, obj) + }); &&& pods.len() == vrs.spec.replicas.unwrap_or(0) } -pub open spec fn owned_selector_match_is(vrs: VReplicaSetView, resources: StoredState, key: ObjectRef) -> bool { - let obj = resources[key]; - &&& resources.contains_key(key) +pub open spec fn owned_selector_match_is(vrs: VReplicaSetView, obj: DynamicObjectView) -> bool { &&& obj.kind == PodView::kind() &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) From 7a60faee211e1691b30b45008609d7e50adf9d4d Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 3 Sep 2024 14:42:28 -0500 Subject: [PATCH 14/22] Verify WF1 for after send list pods req ~> recieve list pods resp Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 186 +++++++++++++++--- .../proof/predicate.rs | 46 +++-- 2 files changed, 193 insertions(+), 39 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 3980bd113..ff0974671 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -19,7 +19,7 @@ use crate::v_replica_set_controller::{ proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, string::*, math::abs}; +use vstd::{prelude::*, set_lib::*, string::*, math::abs}; verus! { @@ -29,6 +29,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( requires spec.entails(always(lift_action(VRSCluster::next()))), spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), spec.entails(always(lift_state(VRSCluster::crash_disabled()))), spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), @@ -49,12 +50,39 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( ).leads_to(lift_state(current_state_matches(vrs))) ) { + let invariants = { + &&& spec.entails(always(lift_action(VRSCluster::next()))) + &&& spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))) + &&& spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))) + &&& spec.entails(always(lift_state(VRSCluster::crash_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) + &&& spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( + vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))) + &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) + }; let pre = |diff: int| lift_state( |s: VRSCluster| { &&& no_pending_req_at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::Init)(s) &&& num_diff_pods_is(vrs, diff)(s) } ); + let list_req = |diff: int| lift_state( + |s: VRSCluster| { + &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); + let list_req_msg = |req_msg: VRSMessage, diff: int| lift_state( + |s: VRSCluster| { + &&& req_msg_is_the_in_flight_list_req_at_after_list_pods_step(vrs, req_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + } + ); let list_resp = |diff: int| lift_state( |s: VRSCluster| { &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) @@ -64,19 +92,28 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( // Deal with transition from init to listing the pods. lemma_from_init_step_to_send_list_pods_req(spec, vrs, diff); - lemma_from_after_send_list_pods_req_to_receive_list_pods_resp(spec, vrs, diff); - // TODO: Would an invariant be a better way of showing - // num_diff_pods_is is maintained across this introductory step? + assert forall |req_msg: VRSMessage| + invariants implies #[trigger] spec.entails(list_req_msg(req_msg, diff).leads_to(list_resp(diff))) by { + lemma_from_after_send_list_pods_req_to_receive_list_pods_resp(spec, vrs, req_msg, diff); + }; + leads_to_exists_intro(spec, |req_msg| list_req_msg(req_msg, diff), list_resp(diff)); + + + assert_by(spec.entails(list_req(diff).leads_to(tla_exists(|req_msg| list_req_msg(req_msg, diff)))), { + assert forall |ex| #[trigger] list_req(diff).satisfied_by(ex) + implies tla_exists(|req_msg| list_req_msg(req_msg, diff)).satisfied_by(ex) by { + let req_msg = ex.head().ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); + assert((|req_msg: VRSMessage| list_req_msg(req_msg, diff))(req_msg).satisfied_by(ex)); + }; + valid_implies_implies_leads_to(spec, list_req(diff), tla_exists(|req_msg| list_req_msg(req_msg, diff))); + }); + leads_to_trans_n!( spec, pre(diff), - lift_state( - |s: VRSCluster| { - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) - &&& num_diff_pods_is(vrs, diff)(s) - } - ), + list_req(diff), + tla_exists(|req_msg| list_req_msg(req_msg, diff)), list_resp(diff) ); @@ -313,8 +350,8 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { let step = choose |step| VRSCluster::next_step(s, s_prime, step); match step { - Step::ApiServerStep(msg) => { - let msg = msg.unwrap(); + Step::ApiServerStep(input) => { + let msg = input.unwrap(); lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( s, s_prime, vrs, diff, msg ); @@ -330,15 +367,25 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( ); } -#[verifier(external_body)] pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( - spec: TempPred, vrs: VReplicaSetView, diff: int + spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), ensures spec.entails( lift_state( |s: VRSCluster| { - &&& pending_req_in_flight_at_after_list_pods_step(vrs)(s) + &&& req_msg_is_the_in_flight_list_req_at_after_list_pods_step(vrs, req_msg)(s) &&& num_diff_pods_is(vrs, diff)(s) } ).leads_to( @@ -351,6 +398,97 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( ) ) { + let pre = |s: VRSCluster| { + &&& req_msg_is_the_in_flight_list_req_at_after_list_pods_step(vrs, req_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = Some(req_msg); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.get_Some_0(); + lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s, s_prime, vrs, diff, msg + ); + // Prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + // Prod for the theorem prover to realize there is a response message. + if msg == req_msg { + let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs == s_prime.resources().values().filter( + |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + } + ).to_seq() + } + }); + assert(post(s_prime)); + } + }, + _ => {} + } + } + + assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_list_response().res.is_Ok() + &&& { + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // The response must give back all the pods in the replicaset's namespace. + resp_objs == s_prime.resources().values().filter( + |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + } + ).to_seq() + } + }); + assert(post(s_prime)); + } + + VRSCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, VRSCluster::handle_request(), pre, post + ); } // Lemmas for creating new pods to cover shortfalls. @@ -440,13 +578,12 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( + resp_objs == s.resources().values().filter( |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() } - ) + ).to_seq() } }; assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); @@ -734,13 +871,12 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( + resp_objs == s.resources().values().filter( |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() } - ) + ).to_seq() } }; assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index c1d16d0ed..4dee19001 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -80,7 +80,28 @@ pub open spec fn pending_req_in_flight_at_after_list_pods_step( let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); let request = msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) + &&& s.in_flight().contains(msg) + &&& msg.src == HostId::CustomController + &&& msg.dst == HostId::ApiServer + &&& msg.content.is_APIRequest() + &&& request.is_ListRequest() + &&& request.get_ListRequest_0() == ListRequest { + kind: PodView::kind(), + namespace: vrs.metadata.namespace.unwrap(), + } + } +} + +pub open spec fn req_msg_is_the_in_flight_list_req_at_after_list_pods_step( + vrs: VReplicaSetView, req_msg: VRSMessage +) -> StatePred { + |s: VRSCluster| { + let step = VReplicaSetReconcileStep::AfterListPods; + let msg = req_msg; + let request = msg.content.get_APIRequest_0(); + &&& at_vrs_step_with_vrs(vrs, step)(s) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) &&& s.in_flight().contains(msg) &&& msg.src == HostId::CustomController &&& msg.dst == HostId::ApiServer @@ -101,8 +122,7 @@ pub open spec fn exists_resp_in_flight_at_after_list_pods_step( let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); let request = msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) - &&& s.in_flight().contains(msg) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) &&& msg.src == HostId::CustomController &&& msg.dst == HostId::ApiServer &&& msg.content.is_APIRequest() @@ -118,13 +138,12 @@ pub open spec fn exists_resp_in_flight_at_after_list_pods_step( &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( + resp_objs == s.resources().values().filter( |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() } - ) + ).to_seq() } } } @@ -138,7 +157,7 @@ pub open spec fn resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step( let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); let request = msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) &&& msg.src == HostId::CustomController &&& msg.dst == HostId::ApiServer &&& msg.content.is_APIRequest() @@ -153,13 +172,12 @@ pub open spec fn resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step( &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); // The response must give back all the pods in the replicaset's namespace. - resp_objs.to_set() == s.resources().values().filter( + resp_objs == s.resources().values().filter( |o: DynamicObjectView| { - &&& o.kind == PodView::kind() - &&& o.metadata.namespace.is_Some() - &&& o.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() } - ) + ).to_seq() } } } From fe14af191ba29f9a9708c255ebba778739d6b370 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 4 Sep 2024 00:34:39 -0500 Subject: [PATCH 15/22] Checkpoint: almost proved next WF1 Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 3 +- .../proof/liveness/api_actions.rs | 10 +- .../proof/liveness/resource_match.rs | 253 +++++++++++++++--- .../proof/liveness/spec.rs | 2 +- .../proof/predicate.rs | 20 +- .../trusted/liveness_theorem.rs | 1 + src/vstd_ext/mod.rs | 1 + src/vstd_ext/seq_lib.rs | 10 + src/vstd_ext/set_lib.rs | 21 ++ 9 files changed, 264 insertions(+), 57 deletions(-) create mode 100644 src/vstd_ext/set_lib.rs diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index f077175b9..1bdaef95f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -28,7 +28,7 @@ pub open spec fn cluster_resources_is_finite() -> StatePred { |s: VRSCluster| s.resources().dom().finite() } -pub open spec fn every_create_request_has_an_empty_deletion_timestamp() -> StatePred { +pub open spec fn every_create_request_is_well_formed() -> StatePred { |s: VRSCluster| { forall |msg: VRSMessage| #![trigger msg.dst.is_ApiServer(), msg.content.is_APIRequest()] { let content = msg.content; @@ -41,6 +41,7 @@ pub open spec fn every_create_request_has_an_empty_deletion_timestamp() -> State let content = msg.content; let obj = content.get_create_request().obj; &&& obj.metadata.deletion_timestamp.is_None() + &&& content.get_create_request().namespace == obj.metadata.namespace.unwrap() } } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs index 0ff1e1282..6896c2b93 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -24,6 +24,14 @@ use vstd::{prelude::*, string::*, map::*, map_lib::*, math::abs}; verus! { +pub open spec fn owned_selector_match_is_1(vrs: VReplicaSetView, obj: DynamicObjectView) -> bool { + &&& obj.kind == PodView::kind() + &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) + &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) + &&& obj.metadata.deletion_timestamp.is_None() +} + pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( s: VRSCluster, s_prime: VRSCluster, vrs: VReplicaSetView, diff: int, msg: VRSMessage, ) @@ -33,7 +41,7 @@ pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_ VRSCluster::busy_disabled()(s), VRSCluster::every_in_flight_msg_has_unique_id()(s), VRSCluster::each_object_in_etcd_is_well_formed()(s), - helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s), + helper_invariants::every_create_request_is_well_formed()(s), helper_invariants::no_pending_update_or_update_status_request_on_pods()(s), helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s), helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s), diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index ff0974671..9c6752ec1 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -13,13 +13,13 @@ use crate::kubernetes_cluster::spec::{ message::*, }; use crate::temporal_logic::{defs::*, rules::*}; -use crate::vstd_ext::{map_lib::*, string_view::*}; +use crate::vstd_ext::{map_lib::*, seq_lib::*, set_lib::*, string_view::*}; use crate::v_replica_set_controller::{ model::{reconciler::*}, proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, set_lib::*, string::*, math::abs}; +use vstd::{prelude::*, set_lib::*, string::*, map_lib::*, math::abs}; verus! { @@ -36,7 +36,8 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), - spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), @@ -60,7 +61,8 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) &&& spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) - &&& spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))) + &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) @@ -289,7 +291,7 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), - spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), @@ -327,7 +329,7 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) &&& VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)(s) - &&& helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) @@ -341,7 +343,7 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), state_is_init)), - lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()), + lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) @@ -377,7 +379,8 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), @@ -413,7 +416,8 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( &&& VRSCluster::busy_disabled()(s) &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) - &&& helper_invariants::every_create_request_has_an_empty_deletion_timestamp()(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) @@ -425,7 +429,8 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( lift_state(VRSCluster::busy_disabled()), lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), - lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) @@ -444,19 +449,47 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( // Prod for the theorem prover to realize there is a response message. if msg == req_msg { let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + + assert forall |o: DynamicObjectView| #![auto] + pre(s) && matching_pod_entries(vrs, s_prime.resources()).values().contains(o) + implies resp_objs.to_set().contains(o) by { + // Tricky reasoning about .to_seq + let selector = |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + }; + let selected_elements = s.resources().values().filter(selector); + assert(selected_elements.contains(o)); + lemma_values_finite(s.resources()); + finite_set_to_seq_contains_all_set_elements(selected_elements); + } + + assert forall |o: DynamicObjectView| #![auto] + pre(s) && resp_objs.contains(o) + implies !PodView::unmarshal(o).is_err() by { + // Tricky reasoning about .to_seq + let selector = |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + }; + let selected_elements = s.resources().values().filter(selector); + lemma_values_finite(s.resources()); + finite_set_to_seq_contains_all_set_elements(selected_elements); + assert(resp_objs == selected_elements.to_seq()); + assert(selected_elements.contains(o)); + } + seq_pred_false_on_all_elements_implies_empty_filter(resp_objs, |o: DynamicObjectView| PodView::unmarshal(o).is_err()); + assert({ &&& s_prime.in_flight().contains(resp_msg) &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s_prime.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s_prime.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } }); assert(post(s_prime)); @@ -468,19 +501,47 @@ pub proof fn lemma_from_after_send_list_pods_req_to_receive_list_pods_resp( assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; + let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + + assert forall |o: DynamicObjectView| #![auto] + pre(s) && matching_pod_entries(vrs, s_prime.resources()).values().contains(o) + implies resp_objs.to_set().contains(o) by { + // Tricky reasoning about .to_seq + let selector = |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + }; + let selected_elements = s.resources().values().filter(selector); + assert(selected_elements.contains(o)); + lemma_values_finite(s.resources()); + finite_set_to_seq_contains_all_set_elements(selected_elements); + } + + assert forall |o: DynamicObjectView| #![auto] + pre(s) && resp_objs.contains(o) + implies !PodView::unmarshal(o).is_err() by { + // Tricky reasoning about .to_seq + let selector = |o: DynamicObjectView| { + &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + &&& o.object_ref().kind == PodView::kind() + }; + let selected_elements = s.resources().values().filter(selector); + lemma_values_finite(s.resources()); + finite_set_to_seq_contains_all_set_elements(selected_elements); + assert(resp_objs == selected_elements.to_seq()); + assert(selected_elements.contains(o)); + } + seq_pred_false_on_all_elements_implies_empty_filter(resp_objs, |o: DynamicObjectView| PodView::unmarshal(o).is_err()); + assert({ &&& s_prime.in_flight().contains(resp_msg) &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s_prime.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s_prime.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } }); assert(post(s_prime)); @@ -577,13 +638,9 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } }; assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); @@ -709,11 +766,23 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp ); } -#[verifier(external_body)] pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) - requires + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -732,6 +801,114 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( ) ), { + let pre = |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = (Some(resp_msg), Some(vrs.object_ref())); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.unwrap(); + lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s, s_prime, vrs, diff, msg + ); + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + Step::ControllerStep(input) => { + if input.0 == Some(resp_msg) && input.1 == Some(vrs.object_ref()) { + + // My brain's too fried to think of anything other than the Dante quote: + // "Abandon all hope ye who enter here". + assume(false); + + // //assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + // let objs = resp_msg.content.get_list_response().res.unwrap(); + // let pods_or_none = objects_to_pods(objs); + // //assert(!pods_or_none.is_none()); + // let pods = pods_or_none.unwrap(); + // let filtered_pods = filter_pods(pods, vrs); + // let replicas = vrs.spec.replicas.unwrap_or(0); + // //assert(!(replicas < 0)); + // let desired_replicas: usize = replicas as usize; + // //let desired_replicas = replicas; + // let new_diff = desired_replicas - filtered_pods.len(); + // let pod = make_pod(vrs); + + // assume(filtered_pods.len() == matching_pods(vrs, s.resources()).len()); + + // assert_by(!(filtered_pods.len() == desired_replicas), { + // assert(matching_pods(vrs, s.resources()).len() - replicas == diff); + // }); + // assert(filtered_pods.len() < desired_replicas); + // assert(new_diff == -diff); + + // // let replicas = vrs.spec.replicas.unwrap_or(0); + // // let objs = resp_msg.content.get_list_response().res.unwrap(); + // // let pods_or_none = objects_to_pods(objs); + // // let pods = pods_or_none.unwrap(); + // // let filtered_pods = filter_pods(pods, vrs); + + // // // assert(resp_msg.content.is_list_response()); + // // // assume(pods_or_none.is_Some()); + // // // assert(num_diff_pods_is(vrs, diff)(s)); + // // // assert(matching_pods(vrs, s.resources()).len() - replicas == diff); + // // // assert(filtered_pods.len() - replicas == diff); + // // // assert(filtered_pods.len() < replicas); + + // // // assert(replicas >= 0); + + // // assert(pods_or_none.is_Some()); + + // // assert(replicas - filtered_pods.len() == abs(diff)); + // let step = VReplicaSetReconcileStep::AfterCreatePod((abs(diff) - 1) as nat as usize); + // assert(at_vrs_step_with_vrs(vrs, step)(s_prime)); + // assert(pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s_prime)); + } + }, + _ => {} + } + } + + VRSCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, VRSCluster::continue_reconcile(), pre, post + ); } #[verifier(external_body)] @@ -870,13 +1047,9 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } }; assert((|resp_msg: VRSMessage| list_resp_msg(resp_msg, diff))(resp_msg).satisfied_by(ex)); diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 3b3150e6e..970bcd0c7 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -114,7 +114,7 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - .and(always(lift_state(helper_invariants::every_create_request_has_an_empty_deletion_timestamp()))) + .and(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) .and(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) .and(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index 4dee19001..4bffbafe5 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -137,13 +137,9 @@ pub open spec fn exists_resp_in_flight_at_after_list_pods_step( &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } } } @@ -171,13 +167,9 @@ pub open spec fn resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step( &&& resp_msg.content.get_list_response().res.is_Ok() &&& { let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // The response must give back all the pods in the replicaset's namespace. - resp_objs == s.resources().values().filter( - |o: DynamicObjectView| { - &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - &&& o.object_ref().kind == PodView::kind() - } - ).to_seq() + // The matching pods must be a subset of the response. + &&& matching_pod_entries(vrs, s.resources()).values().subset_of(resp_objs.to_set()) + &&& objects_to_pods(resp_objs).is_Some() } } } diff --git a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs index eaeb4ce58..7a890e248 100644 --- a/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs +++ b/src/controller_examples/v_replica_set_controller/trusted/liveness_theorem.rs @@ -37,6 +37,7 @@ pub open spec fn resource_state_matches(vrs: VReplicaSetView, resources: StoredS pub open spec fn owned_selector_match_is(vrs: VReplicaSetView, obj: DynamicObjectView) -> bool { &&& obj.kind == PodView::kind() + &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) &&& obj.metadata.deletion_timestamp.is_None() diff --git a/src/vstd_ext/mod.rs b/src/vstd_ext/mod.rs index ddafd5333..6a7c3033a 100644 --- a/src/vstd_ext/mod.rs +++ b/src/vstd_ext/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT pub mod map_lib; pub mod multiset_lib; +pub mod set_lib; pub mod seq_lib; pub mod string_map; pub mod string_view; diff --git a/src/vstd_ext/seq_lib.rs b/src/vstd_ext/seq_lib.rs index 7be66b4d6..4e116c6d9 100644 --- a/src/vstd_ext/seq_lib.rs +++ b/src/vstd_ext/seq_lib.rs @@ -103,4 +103,14 @@ pub proof fn seq_unequal_preserved_by_add_auto(suffix: Seq) }; } +#[verifier(external_body)] +pub proof fn seq_pred_false_on_all_elements_implies_empty_filter(s: Seq, pred: spec_fn(A) -> bool) + requires forall |e: A| #![auto] s.contains(e) ==> !pred(e), + ensures s.filter(pred).len() == 0; +// +// @Xudong Sun take a look. +// +// If `pred` is false on every element, filter will return an empty sequence. +// + } diff --git a/src/vstd_ext/set_lib.rs b/src/vstd_ext/set_lib.rs new file mode 100644 index 000000000..229148ff2 --- /dev/null +++ b/src/vstd_ext/set_lib.rs @@ -0,0 +1,21 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use vstd::prelude::*; +use vstd::set::*; +use vstd::set_lib::*; + +verus! { + +#[verifier(external_body)] +pub proof fn finite_set_to_seq_contains_all_set_elements(s: Set) + requires s.finite(), + ensures forall |e: A| #![auto] s.contains(e) <==> s.to_seq().contains(e); +// +// @Xudong Sun take a look. +// +// Anything in a finite set will be in a sequence composed of its elements; likewise +// anything in that constructed sequence will be part of the original set. +// + +} From 7b9a4e937c43f28d7995d83526cfb8686f7a9b9b Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Wed, 4 Sep 2024 13:19:40 -0500 Subject: [PATCH 16/22] Isolate WF1 proof issue to single lemma Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 25 ++ .../proof/liveness/api_actions.rs | 8 - .../proof/liveness/resource_match.rs | 267 ++++++++++++++---- .../proof/liveness/spec.rs | 1 + .../proof/predicate.rs | 4 +- 5 files changed, 247 insertions(+), 58 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 1bdaef95f..0521370a7 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -28,6 +28,31 @@ pub open spec fn cluster_resources_is_finite() -> StatePred { |s: VRSCluster| s.resources().dom().finite() } +// The proof will probabily involve more changes elsewhere. +pub open spec fn vrs_replicas_bounded_above( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + vrs.spec.replicas.unwrap_or(0) <= i32::MAX // As allowed by Kubernetes. + } +} + +pub open spec fn vrs_selector_matches_template_labels( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + let match_value = + if vrs.spec.template.is_none() + || vrs.spec.template.unwrap().metadata.is_none() + || vrs.spec.template.unwrap().metadata.unwrap().labels.is_none() { + Map::empty() + } else { + vrs.spec.template.unwrap().metadata.unwrap().labels.unwrap() + }; + vrs.spec.selector.matches(match_value) + } +} + pub open spec fn every_create_request_is_well_formed() -> StatePred { |s: VRSCluster| { forall |msg: VRSMessage| #![trigger msg.dst.is_ApiServer(), msg.content.is_APIRequest()] { diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs index 6896c2b93..55f4f63ab 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -24,14 +24,6 @@ use vstd::{prelude::*, string::*, map::*, map_lib::*, math::abs}; verus! { -pub open spec fn owned_selector_match_is_1(vrs: VReplicaSetView, obj: DynamicObjectView) -> bool { - &&& obj.kind == PodView::kind() - &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() - &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) - &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) - &&& obj.metadata.deletion_timestamp.is_None() -} - pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( s: VRSCluster, s_prime: VRSCluster, vrs: VReplicaSetView, diff: int, msg: VRSMessage, ) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 9c6752ec1..5ae351551 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -6,6 +6,7 @@ use crate::kubernetes_api_objects::spec::{ api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, }; use crate::kubernetes_cluster::spec::{ + api_server::state_machine::generate_name, builtin_controllers::types::BuiltinControllerChoice, cluster::*, cluster_state_machine::Step, @@ -19,7 +20,7 @@ use crate::v_replica_set_controller::{ proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, set_lib::*, string::*, map_lib::*, math::abs}; +use vstd::{prelude::*, set_lib::*, string::*, map::*, map_lib::*, math::abs}; verus! { @@ -34,9 +35,12 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -59,9 +63,12 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) + &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) &&& spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) @@ -557,6 +564,21 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -575,6 +597,23 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( ) ), { + let invariants = { + &&& spec.entails(always(lift_action(VRSCluster::next()))) + &&& spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))) + &&& spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))) + &&& spec.entails(always(lift_state(VRSCluster::crash_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) + &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) + &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) + &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) + }; let list_resp = |diff: int| lift_state( |s: VRSCluster| { &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) @@ -779,6 +818,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -819,6 +859,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_replicas_bounded_above(vrs)(s) &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) @@ -834,6 +875,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)), lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), @@ -853,53 +895,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( }, Step::ControllerStep(input) => { if input.0 == Some(resp_msg) && input.1 == Some(vrs.object_ref()) { - - // My brain's too fried to think of anything other than the Dante quote: - // "Abandon all hope ye who enter here". - assume(false); - - // //assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); - // let objs = resp_msg.content.get_list_response().res.unwrap(); - // let pods_or_none = objects_to_pods(objs); - // //assert(!pods_or_none.is_none()); - // let pods = pods_or_none.unwrap(); - // let filtered_pods = filter_pods(pods, vrs); - // let replicas = vrs.spec.replicas.unwrap_or(0); - // //assert(!(replicas < 0)); - // let desired_replicas: usize = replicas as usize; - // //let desired_replicas = replicas; - // let new_diff = desired_replicas - filtered_pods.len(); - // let pod = make_pod(vrs); - - // assume(filtered_pods.len() == matching_pods(vrs, s.resources()).len()); - - // assert_by(!(filtered_pods.len() == desired_replicas), { - // assert(matching_pods(vrs, s.resources()).len() - replicas == diff); - // }); - // assert(filtered_pods.len() < desired_replicas); - // assert(new_diff == -diff); - - // // let replicas = vrs.spec.replicas.unwrap_or(0); - // // let objs = resp_msg.content.get_list_response().res.unwrap(); - // // let pods_or_none = objects_to_pods(objs); - // // let pods = pods_or_none.unwrap(); - // // let filtered_pods = filter_pods(pods, vrs); - - // // // assert(resp_msg.content.is_list_response()); - // // // assume(pods_or_none.is_Some()); - // // // assert(num_diff_pods_is(vrs, diff)(s)); - // // // assert(matching_pods(vrs, s.resources()).len() - replicas == diff); - // // // assert(filtered_pods.len() - replicas == diff); - // // // assert(filtered_pods.len() < replicas); - - // // // assert(replicas >= 0); - - // // assert(pods_or_none.is_Some()); - - // // assert(replicas - filtered_pods.len() == abs(diff)); - // let step = VReplicaSetReconcileStep::AfterCreatePod((abs(diff) - 1) as nat as usize); - // assert(at_vrs_step_with_vrs(vrs, step)(s_prime)); - // assert(pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s_prime)); + lemma_filtered_pods_len_equals_matching_pods_len(s, vrs, resp_msg); } }, _ => {} @@ -912,10 +908,43 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( } #[verifier(external_body)] +pub proof fn lemma_filtered_pods_len_equals_matching_pods_len( + s: VRSCluster, vrs: VReplicaSetView, resp_msg: VRSMessage +) + ensures + ({ + let objs = resp_msg.content.get_list_response().res.unwrap(); + let pods_or_none = objects_to_pods(objs); + let pods = pods_or_none.unwrap(); + let filtered_pods = filter_pods(pods, vrs); + filtered_pods.len() == matching_pods(vrs, s.resources()).len() + }), +{} +// +// @Xudong Sun take a look. +// +// filter_pods filters pods in a very similar way to matching_pods, but the former +// works on a sequence of PodViews, while the latter works on the key-value store directly. +// I'll probably come back to this soon. +// + + pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) requires + // spec.entails(always(lift_action(VRSCluster::next()))), + // spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + // spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + // spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + // spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + // spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + // spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + // spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + // spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + // spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + // spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + // spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -934,6 +963,148 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( ) ), { + assume(false); + let pre = |s: VRSCluster| { + &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff + 1)(s) + }; + let input = Some(req_msg); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_selector_matches_template_labels(vrs)(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.get_Some_0(); + // Case 1: We're processing the create request + if msg == req_msg { + let debug_osm_is = |obj: DynamicObjectView| { + &&& obj.kind == PodView::kind() + //bad &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + //&&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) + &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) + //bad &&& obj.metadata.deletion_timestamp.is_None() + }; + let debug_osm_is_on_podview = |obj: PodView| { + //&&& obj.kind == PodView::kind() + //bad &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() + &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) + // &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) + //bad &&& obj.metadata.deletion_timestamp.is_None() + }; + let resp_msg = VRSCluster::handle_create_request_msg(req_msg, s.kubernetes_api_state).1; + let resp = resp_msg.content.get_create_response(); + let req = req_msg.content.get_create_request(); + assert(!(req.obj.metadata.name.is_None() && req.obj.metadata.generate_name.is_None())); + assert(!(req.obj.metadata.namespace.is_Some() && req.namespace != req.obj.metadata.namespace.get_Some_0())); + assert(crate::kubernetes_cluster::spec::api_server::state_machine::create_request_admission_check::(req_msg.content.get_create_request(), s.kubernetes_api_state).is_none()); + assert(req.obj == make_pod(vrs).marshal()); + assert(make_pod(vrs).metadata.owner_references.unwrap()[0] == vrs.controller_owner_ref()); + assert(debug_osm_is(make_pod(vrs).marshal())); + let key = ObjectRef{ + kind: req.obj.kind, + name: if req.obj.metadata.name.is_Some() { + req.obj.metadata.name.unwrap() + } else { + generate_name(s.kubernetes_api_state) + }, + namespace: req.namespace, + }; + assert_maps_equal!(s.resources().remove(key) == s_prime.resources().remove(key)); + assert(s_prime.resources().contains_key(key)); + assert(owned_selector_match_is(vrs, s_prime.resources()[key])); + assert(post(s_prime)); + } else { + assume(false); + } + }, + _ => {} + } + } + + assume(false); + + // assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + // let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; + // let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + + // assert forall |o: DynamicObjectView| #![auto] + // pre(s) && matching_pod_entries(vrs, s_prime.resources()).values().contains(o) + // implies resp_objs.to_set().contains(o) by { + // // Tricky reasoning about .to_seq + // let selector = |o: DynamicObjectView| { + // &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + // &&& o.object_ref().kind == PodView::kind() + // }; + // let selected_elements = s.resources().values().filter(selector); + // assert(selected_elements.contains(o)); + // lemma_values_finite(s.resources()); + // finite_set_to_seq_contains_all_set_elements(selected_elements); + // } + + // assert forall |o: DynamicObjectView| #![auto] + // pre(s) && resp_objs.contains(o) + // implies !PodView::unmarshal(o).is_err() by { + // // Tricky reasoning about .to_seq + // let selector = |o: DynamicObjectView| { + // &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() + // &&& o.object_ref().kind == PodView::kind() + // }; + // let selected_elements = s.resources().values().filter(selector); + // lemma_values_finite(s.resources()); + // finite_set_to_seq_contains_all_set_elements(selected_elements); + // assert(resp_objs == selected_elements.to_seq()); + // assert(selected_elements.contains(o)); + // } + // seq_pred_false_on_all_elements_implies_empty_filter(resp_objs, |o: DynamicObjectView| PodView::unmarshal(o).is_err()); + + // assert({ + // &&& s_prime.in_flight().contains(resp_msg) + // &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + // &&& resp_msg.content.get_list_response().res.is_Ok() + // &&& { + // let resp_objs = resp_msg.content.get_list_response().res.unwrap(); + // // The matching pods must be a subset of the response. + // &&& matching_pod_entries(vrs, s_prime.resources()).values().subset_of(resp_objs.to_set()) + // &&& objects_to_pods(resp_objs).is_Some() + // } + // }); + // assert(post(s_prime)); + // } + + // VRSCluster::lemma_pre_leads_to_post_by_kubernetes_api( + // spec, input, stronger_next, VRSCluster::handle_request(), pre, post + // ); } #[verifier(external_body)] diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 970bcd0c7..ccd9ee40f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -114,6 +114,7 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + .and(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) .and(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) .and(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index 4bffbafe5..c67c1843c 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -183,7 +183,7 @@ pub open spec fn pending_req_in_flight_at_after_create_pod_step( let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); let request = msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) &&& s.in_flight().contains(msg) &&& msg.src == HostId::CustomController &&& msg.dst == HostId::ApiServer @@ -203,7 +203,7 @@ pub open spec fn req_msg_is_the_in_flight_create_request_at_after_create_pod_ste let step = VReplicaSetReconcileStep::AfterCreatePod(diff as usize); let request = req_msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), req_msg) &&& s.in_flight().contains(req_msg) &&& req_msg.src == HostId::CustomController &&& req_msg.dst == HostId::ApiServer From 5f2d5e5b6f1bc9936c75a3fbe7f57b3124c3491b Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 10 Sep 2024 16:20:58 -0500 Subject: [PATCH 17/22] Show WF1 for from create request to create response Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 25 +- .../proof/liveness/api_actions.rs | 25 ++ .../proof/liveness/resource_match.rs | 241 ++++++++++-------- .../proof/liveness/spec.rs | 1 + src/vstd_ext/map_lib.rs | 13 + 5 files changed, 202 insertions(+), 103 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 0521370a7..c4fef11d1 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -7,6 +7,7 @@ use crate::kubernetes_api_objects::spec::{ stateful_set::*, }; use crate::kubernetes_cluster::spec::{ + api_server::state_machine::*, cluster::*, cluster_state_machine::Step, controller::types::{ControllerActionInput, ControllerStep}, @@ -64,9 +65,31 @@ pub open spec fn every_create_request_is_well_formed() -> StatePred &&& content.is_create_request() } ==> { let content = msg.content; - let obj = content.get_create_request().obj; + let req = content.get_create_request(); + let obj = req.obj; + let created_obj = DynamicObjectView { + kind: req.obj.kind, + metadata: ObjectMetaView { + // Set name for new object if name is not provided, here we generate + // a unique name. The uniqueness is guaranteed by generated_name_is_unique. + name: if req.obj.metadata.name.is_Some() { + req.obj.metadata.name + } else { + Some(generate_name(s.kubernetes_api_state)) + }, + namespace: Some(req.namespace), // Set namespace for new object + resource_version: Some(s.kubernetes_api_state.resource_version_counter), // Set rv for new object + uid: Some(s.kubernetes_api_state.uid_counter), // Set uid for new object + deletion_timestamp: None, // Unset deletion timestamp for new object + ..req.obj.metadata + }, + spec: req.obj.spec, + status: marshalled_default_status::(req.obj.kind), // Overwrite the status with the default one + }; &&& obj.metadata.deletion_timestamp.is_None() &&& content.get_create_request().namespace == obj.metadata.namespace.unwrap() + &&& unmarshallable_object::(obj) + &&& created_object_validity_check::(created_obj).is_none() } } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs index 55f4f63ab..b85efccfd 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -76,4 +76,29 @@ pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_ }; } +#[verifier(external_body)] +pub proof fn lemma_api_request_not_on_matching_pods_maintains_matching_pods( + s: VRSCluster, s_prime: VRSCluster, vrs: VReplicaSetView, diff: int, msg: VRSMessage, req_msg: VRSMessage, +) + requires + msg != req_msg, + req_msg == s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(), + VRSCluster::next_step(s, s_prime, Step::ApiServerStep(Some(msg))), + VRSCluster::crash_disabled()(s), + VRSCluster::busy_disabled()(s), + VRSCluster::every_in_flight_msg_has_unique_id()(s), + VRSCluster::each_object_in_etcd_is_well_formed()(s), + helper_invariants::every_create_request_is_well_formed()(s), + helper_invariants::no_pending_update_or_update_status_request_on_pods()(s), + helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s), + helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s), + ensures + matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources()); +// +// @Xudong Sun take a look +// +// This is very similar to the proof above, but we'll need to explicitly handle the case that we're on +// an after create or delete pod step. +// + } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 5ae351551..24caa1a8c 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -6,7 +6,7 @@ use crate::kubernetes_api_objects::spec::{ api_method::*, common::*, dynamic::*, owner_reference::*, prelude::*, resource::*, }; use crate::kubernetes_cluster::spec::{ - api_server::state_machine::generate_name, + api_server::state_machine::*, builtin_controllers::types::BuiltinControllerChoice, cluster::*, cluster_state_machine::Step, @@ -20,7 +20,7 @@ use crate::v_replica_set_controller::{ proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, set_lib::*, string::*, map::*, map_lib::*, math::abs}; +use vstd::{prelude::*, seq::*, seq_lib::*, set_lib::*, string::*, map::*, map_lib::*, math::abs}; verus! { @@ -41,6 +41,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -69,6 +70,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) @@ -575,6 +577,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -609,6 +612,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) @@ -711,7 +715,23 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) - requires + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -730,6 +750,24 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp ) ), { + let invariants = { + &&& spec.entails(always(lift_action(VRSCluster::next()))) + &&& spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))) + &&& spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))) + &&& spec.entails(always(lift_state(VRSCluster::crash_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) + &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) + &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) + &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) + &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) + &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) + &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) + }; let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) @@ -933,18 +971,18 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) requires - // spec.entails(always(lift_action(VRSCluster::next()))), - // spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), - // spec.entails(always(lift_state(VRSCluster::crash_disabled()))), - // spec.entails(always(lift_state(VRSCluster::busy_disabled()))), - // spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), - // spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), - // spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - // spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), - // spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), - // spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), - // spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), - // spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -963,7 +1001,6 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( ) ), { - assume(false); let pre = |s: VRSCluster| { &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) @@ -1008,103 +1045,103 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( let msg = input.get_Some_0(); // Case 1: We're processing the create request if msg == req_msg { - let debug_osm_is = |obj: DynamicObjectView| { - &&& obj.kind == PodView::kind() - //bad &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() - //&&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) - &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) - //bad &&& obj.metadata.deletion_timestamp.is_None() - }; - let debug_osm_is_on_podview = |obj: PodView| { - //&&& obj.kind == PodView::kind() - //bad &&& obj.metadata.namespace.unwrap() == vrs.metadata.namespace.unwrap() - &&& obj.metadata.owner_references_contains(vrs.controller_owner_ref()) - // &&& vrs.spec.selector.matches(obj.metadata.labels.unwrap_or(Map::empty())) - //bad &&& obj.metadata.deletion_timestamp.is_None() - }; let resp_msg = VRSCluster::handle_create_request_msg(req_msg, s.kubernetes_api_state).1; - let resp = resp_msg.content.get_create_response(); let req = req_msg.content.get_create_request(); - assert(!(req.obj.metadata.name.is_None() && req.obj.metadata.generate_name.is_None())); - assert(!(req.obj.metadata.namespace.is_Some() && req.namespace != req.obj.metadata.namespace.get_Some_0())); - assert(crate::kubernetes_cluster::spec::api_server::state_machine::create_request_admission_check::(req_msg.content.get_create_request(), s.kubernetes_api_state).is_none()); - assert(req.obj == make_pod(vrs).marshal()); - assert(make_pod(vrs).metadata.owner_references.unwrap()[0] == vrs.controller_owner_ref()); - assert(debug_osm_is(make_pod(vrs).marshal())); - let key = ObjectRef{ + let created_obj = DynamicObjectView { kind: req.obj.kind, - name: if req.obj.metadata.name.is_Some() { - req.obj.metadata.name.unwrap() - } else { - generate_name(s.kubernetes_api_state) + metadata: ObjectMetaView { + // Set name for new object if name is not provided, here we generate + // a unique name. The uniqueness is guaranteed by generated_name_is_unique. + name: if req.obj.metadata.name.is_Some() { + req.obj.metadata.name + } else { + Some(generate_name(s.kubernetes_api_state)) + }, + namespace: Some(req.namespace), // Set namespace for new object + resource_version: Some(s.kubernetes_api_state.resource_version_counter), // Set rv for new object + uid: Some(s.kubernetes_api_state.uid_counter), // Set uid for new object + deletion_timestamp: None, // Unset deletion timestamp for new object + ..req.obj.metadata }, - namespace: req.namespace, + spec: req.obj.spec, + status: marshalled_default_status::(req.obj.kind), // Overwrite the status with the default one }; - assert_maps_equal!(s.resources().remove(key) == s_prime.resources().remove(key)); - assert(s_prime.resources().contains_key(key)); - assert(owned_selector_match_is(vrs, s_prime.resources()[key])); - assert(post(s_prime)); + let key = created_obj.object_ref(); + + // Asserts properties about the response message + generated_name_is_unique(s.kubernetes_api_state); + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + }); + + // Asserts properties about the newly inserted object. + assert_maps_equal!(s.resources().insert(key, created_obj) == s_prime.resources()); + assert(s.resources().len() + 1 == s_prime.resources().len()); + assert(created_obj.metadata.owner_references.unwrap()[0] == vrs.controller_owner_ref()); + assert(owned_selector_match_is(vrs, created_obj)); + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is increased. + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + assert(matching_pod_entries(vrs, s.resources()).insert(key, created_obj) == matching_pod_entries(vrs, s_prime.resources())); } else { - assume(false); - } + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + lemma_api_request_not_on_matching_pods_maintains_matching_pods( + s, s_prime, vrs, diff, msg, req_msg, + ); + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); } }, _ => {} } } - assume(false); - - // assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { - // let resp_msg = VRSCluster::handle_list_request_msg(req_msg, s.kubernetes_api_state).1; - // let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - - // assert forall |o: DynamicObjectView| #![auto] - // pre(s) && matching_pod_entries(vrs, s_prime.resources()).values().contains(o) - // implies resp_objs.to_set().contains(o) by { - // // Tricky reasoning about .to_seq - // let selector = |o: DynamicObjectView| { - // &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - // &&& o.object_ref().kind == PodView::kind() - // }; - // let selected_elements = s.resources().values().filter(selector); - // assert(selected_elements.contains(o)); - // lemma_values_finite(s.resources()); - // finite_set_to_seq_contains_all_set_elements(selected_elements); - // } - - // assert forall |o: DynamicObjectView| #![auto] - // pre(s) && resp_objs.contains(o) - // implies !PodView::unmarshal(o).is_err() by { - // // Tricky reasoning about .to_seq - // let selector = |o: DynamicObjectView| { - // &&& o.object_ref().namespace == vrs.metadata.namespace.unwrap() - // &&& o.object_ref().kind == PodView::kind() - // }; - // let selected_elements = s.resources().values().filter(selector); - // lemma_values_finite(s.resources()); - // finite_set_to_seq_contains_all_set_elements(selected_elements); - // assert(resp_objs == selected_elements.to_seq()); - // assert(selected_elements.contains(o)); - // } - // seq_pred_false_on_all_elements_implies_empty_filter(resp_objs, |o: DynamicObjectView| PodView::unmarshal(o).is_err()); - - // assert({ - // &&& s_prime.in_flight().contains(resp_msg) - // &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) - // &&& resp_msg.content.get_list_response().res.is_Ok() - // &&& { - // let resp_objs = resp_msg.content.get_list_response().res.unwrap(); - // // The matching pods must be a subset of the response. - // &&& matching_pod_entries(vrs, s_prime.resources()).values().subset_of(resp_objs.to_set()) - // &&& objects_to_pods(resp_objs).is_Some() - // } - // }); - // assert(post(s_prime)); - // } - - // VRSCluster::lemma_pre_leads_to_post_by_kubernetes_api( - // spec, input, stronger_next, VRSCluster::handle_request(), pre, post - // ); + assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + let resp_msg = VRSCluster::handle_create_request_msg(req_msg, s.kubernetes_api_state).1; + let req = req_msg.content.get_create_request(); + let created_obj = DynamicObjectView { + kind: req.obj.kind, + metadata: ObjectMetaView { + // Set name for new object if name is not provided, here we generate + // a unique name. The uniqueness is guaranteed by generated_name_is_unique. + name: if req.obj.metadata.name.is_Some() { + req.obj.metadata.name + } else { + Some(generate_name(s.kubernetes_api_state)) + }, + namespace: Some(req.namespace), // Set namespace for new object + resource_version: Some(s.kubernetes_api_state.resource_version_counter), // Set rv for new object + uid: Some(s.kubernetes_api_state.uid_counter), // Set uid for new object + deletion_timestamp: None, // Unset deletion timestamp for new object + ..req.obj.metadata + }, + spec: req.obj.spec, + status: marshalled_default_status::(req.obj.kind), // Overwrite the status with the default one + }; + let key = created_obj.object_ref(); + + // Asserts properties about the response message + generated_name_is_unique(s.kubernetes_api_state); + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_create_response().res.is_Ok() + }); + + // Asserts properties about the newly inserted object. + assert_maps_equal!(s.resources().insert(key, created_obj) == s_prime.resources()); + assert(s.resources().len() + 1 == s_prime.resources().len()); + assert(created_obj.metadata.owner_references.unwrap()[0] == vrs.controller_owner_ref()); + assert(owned_selector_match_is(vrs, created_obj)); + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is increased. + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + assert(matching_pod_entries(vrs, s.resources()).insert(key, created_obj) == matching_pod_entries(vrs, s_prime.resources())); + } + + VRSCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, VRSCluster::handle_request(), pre, post + ); } #[verifier(external_body)] diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index ccd9ee40f..84d39f483 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -115,6 +115,7 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) .and(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + .and(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) .and(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) .and(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) diff --git a/src/vstd_ext/map_lib.rs b/src/vstd_ext/map_lib.rs index 6a7d981ea..755f3c991 100644 --- a/src/vstd_ext/map_lib.rs +++ b/src/vstd_ext/map_lib.rs @@ -10,4 +10,17 @@ pub open spec fn map_to_seq(m: Map, f: spec_fn(V) -> bool) -> Seq m.values().filter(f).to_seq() } +#[verifier(external_body)] +pub proof fn a_submap_of_a_finite_map_is_finite(m1: Map, m2: Map) + requires + m1.submap_of(m2), + m2.dom().finite(), + ensures m1.dom().finite(); +// +// @Xudong Sun take a look. +// +// A submap of a finite map is finite -- prove this by showing m1.dom() <= m2.dom(). +// + + } From 0ab15401f348c720cf5fccb83742143f8ba4d1cd Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 10 Sep 2024 17:36:18 -0500 Subject: [PATCH 18/22] Prove WF1 lemma for ok response to create pods Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 24caa1a8c..87b0c2bb2 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -1144,11 +1144,24 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( ); } -#[verifier(external_body)] pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff < 0, ensures spec.entails( @@ -1167,6 +1180,63 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( ) ), { + let pre = |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& pending_req_in_flight_at_after_create_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = (Some(resp_msg), Some(vrs.object_ref())); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_replicas_bounded_above(vrs)(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + // TODO: This could be sped up a bit by extra annotations, but it goes through automatically. + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + _ => {} + } + } + + VRSCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, VRSCluster::continue_reconcile(), pre, post + ); } // Lemmas for deleting excess pods. From 5ae92b841b7ea76121723aff951602c30fbe5574 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 10 Sep 2024 19:55:48 -0500 Subject: [PATCH 19/22] Verify after list pods leads to after send delete request Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 25 ++- .../proof/liveness/resource_match.rs | 147 ++++++++++++++++-- .../proof/liveness/spec.rs | 2 +- .../proof/predicate.rs | 11 +- 4 files changed, 161 insertions(+), 24 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index c4fef11d1..31a2b4d72 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -30,11 +30,11 @@ pub open spec fn cluster_resources_is_finite() -> StatePred { } // The proof will probabily involve more changes elsewhere. -pub open spec fn vrs_replicas_bounded_above( +pub open spec fn vrs_replicas_bounded( vrs: VReplicaSetView ) -> StatePred { |s: VRSCluster| { - vrs.spec.replicas.unwrap_or(0) <= i32::MAX // As allowed by Kubernetes. + 0 <= vrs.spec.replicas.unwrap_or(0) <= i32::MAX // As allowed by Kubernetes. } } @@ -148,4 +148,25 @@ pub open spec fn every_delete_matching_pod_request_implies_at_after_delete_pod_s } } +pub open spec fn at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + forall |diff: usize| { + #[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff))(s) + } ==> { + let state = s.ongoing_reconciles()[vrs.object_ref()].local_state; + let filtered_pods = state.filtered_pods.unwrap(); + let filtered_pod_keys = filtered_pods.map_values(|p: PodView| p.object_ref()); + &&& s.ongoing_reconciles().contains_key(vrs.object_ref()) + &&& state.filtered_pods.is_Some() + &&& diff <= filtered_pod_keys.len() + &&& filtered_pod_keys.no_duplicates() + &&& forall |i| #![auto] 0 <= i < diff ==> { + &&& matching_pod_entries(vrs, s.resources()).contains_key(filtered_pod_keys[i]) + } + } + } +} + } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 87b0c2bb2..b6e7da5fe 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -40,7 +40,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), @@ -69,7 +69,7 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( &&& spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) @@ -576,7 +576,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), @@ -611,7 +611,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) @@ -726,7 +726,7 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), @@ -761,7 +761,7 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) @@ -856,7 +856,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -897,7 +897,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) &&& helper_invariants::cluster_resources_is_finite()(s) - &&& helper_invariants::vrs_replicas_bounded_above(vrs)(s) + &&& helper_invariants::vrs_replicas_bounded(vrs)(s) &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) @@ -913,7 +913,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_create_pod_req( lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), lift_state(helper_invariants::cluster_resources_is_finite()), - lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)), + lift_state(helper_invariants::vrs_replicas_bounded(vrs)), lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), @@ -1157,7 +1157,7 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), - spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -1198,7 +1198,7 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) &&& helper_invariants::cluster_resources_is_finite()(s) - &&& helper_invariants::vrs_replicas_bounded_above(vrs)(s) + &&& helper_invariants::vrs_replicas_bounded(vrs)(s) &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) @@ -1214,7 +1214,7 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), lift_state(helper_invariants::cluster_resources_is_finite()), - lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)), + lift_state(helper_invariants::vrs_replicas_bounded(vrs)), lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), @@ -1226,7 +1226,7 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_create_pod_req( match step { Step::ApiServerStep(input) => { // TODO: This could be sped up a bit by extra annotations, but it goes through automatically. - + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); }, @@ -1244,6 +1244,20 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff > 0, ensures spec.entails( @@ -1453,11 +1467,24 @@ pub proof fn lemma_from_after_receive_delete_pod_resp_to_receive_delete_pod_resp ); } -#[verifier(external_body)] pub proof fn lemma_from_after_receive_list_pods_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff > 0, ensures spec.entails( @@ -1476,8 +1503,100 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_send_delete_pod_req( ) ), { + let pre = |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_list_resp_at_after_list_pods_step(vrs, resp_msg)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = (Some(resp_msg), Some(vrs.object_ref())); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_replicas_bounded(vrs)(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_replicas_bounded(vrs)), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.unwrap(); + lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s, s_prime, vrs, diff, msg + ); + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + Step::ControllerStep(input) => { + if input.0 == Some(resp_msg) && input.1 == Some(vrs.object_ref()) { + let objs = resp_msg.content.get_list_response().res.unwrap(); + let pods_or_none = objects_to_pods(objs); + let pods = pods_or_none.unwrap(); + let filtered_pods = filter_pods(pods, vrs); + lemma_filtered_pods_set_equals_matching_pods(s, vrs, resp_msg); + + let filtered_pods_as_objects = filtered_pods.map_values(|p: PodView| p.marshal()); + let filtered_pods_as_set = filtered_pods_as_objects.to_set(); + assert(filtered_pods_as_objects[diff - 1] == filtered_pods[diff - 1].marshal()); + assert(filtered_pods_as_set.contains(filtered_pods[diff - 1].marshal())); + } + }, + _ => {} + } + } + + VRSCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, VRSCluster::continue_reconcile(), pre, post + ); } +#[verifier(external_body)] +pub proof fn lemma_filtered_pods_set_equals_matching_pods( + s: VRSCluster, vrs: VReplicaSetView, resp_msg: VRSMessage +) + ensures + ({ + let objs = resp_msg.content.get_list_response().res.unwrap(); + let pods_or_none = objects_to_pods(objs); + let pods = pods_or_none.unwrap(); + let filtered_pods = filter_pods(pods, vrs); + &&& filtered_pods.no_duplicates() + &&& filtered_pods.len() == matching_pod_entries(vrs, s.resources()).len() + &&& filtered_pods.map_values(|p: PodView| p.marshal()).to_set() == matching_pod_entries(vrs, s.resources()).values() + }), +{} +// +// This might actually be slightly trickier than the previous one. +// + #[verifier(external_body)] pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 84d39f483..5d1f4e0bd 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -114,7 +114,7 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterCreatePod(k))))))) .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - .and(always(lift_state(helper_invariants::vrs_replicas_bounded_above(vrs)))) + .and(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) .and(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) .and(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index c67c1843c..e16843ef1 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -266,18 +266,15 @@ pub open spec fn resp_msg_is_the_in_flight_ok_resp_at_after_create_pod_step( // Pod deletion predicates -// Placeholder predicate constraining the delete request -// We'll probably need something here ensuring we only delete the -// appropriate keys: this will facilitate modifications by a search-and-replace. +// The delete request must be on a matching pod. pub open spec fn delete_constraint( vrs: VReplicaSetView, req: DeleteRequest ) -> StatePred { |s: VRSCluster| { - true // placeholder + matching_pod_entries(vrs, s.resources()).contains_key(req.key) } } - pub open spec fn pending_req_in_flight_at_after_delete_pod_step( vrs: VReplicaSetView, diff: nat ) -> StatePred { @@ -286,7 +283,7 @@ pub open spec fn pending_req_in_flight_at_after_delete_pod_step( let msg = s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(); let request = msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), msg) &&& s.in_flight().contains(msg) &&& msg.src == HostId::CustomController &&& msg.dst == HostId::ApiServer @@ -303,7 +300,7 @@ pub open spec fn req_msg_is_the_in_flight_delete_request_at_after_delete_pod_ste let step = VReplicaSetReconcileStep::AfterDeletePod(diff as usize); let request = req_msg.content.get_APIRequest_0(); &&& at_vrs_step_with_vrs(vrs, step)(s) - &&& VRSCluster::has_pending_k8s_api_req_msg(s, vrs.object_ref()) + &&& VRSCluster::pending_req_msg_is(s, vrs.object_ref(), req_msg) &&& s.in_flight().contains(req_msg) &&& req_msg.src == HostId::CustomController &&& req_msg.dst == HostId::ApiServer From 6fa99488a449809c28dcdc5c044078bc798ee46b Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 10 Sep 2024 20:38:34 -0500 Subject: [PATCH 20/22] Prove create pod req leads to create pod resp Signed-off-by: Cody Rivera --- .../proof/liveness/resource_match.rs | 122 +++++++++++++++++- .../proof/predicate.rs | 2 - 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index b6e7da5fe..4aef79c28 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -1246,6 +1246,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( requires spec.entails(always(lift_action(VRSCluster::next()))), spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), spec.entails(always(lift_state(VRSCluster::crash_disabled()))), spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), @@ -1254,6 +1255,7 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -1373,7 +1375,19 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_delete_pod_resp( pub proof fn lemma_from_after_receive_delete_pod_resp_to_receive_delete_pod_resp( spec: TempPred, vrs: VReplicaSetView, diff: int ) - requires + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff > 0, ensures spec.entails( @@ -1597,11 +1611,23 @@ pub proof fn lemma_filtered_pods_set_equals_matching_pods( // This might actually be slightly trickier than the previous one. // -#[verifier(external_body)] + pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( spec: TempPred, vrs: VReplicaSetView, req_msg: VRSMessage, diff: int ) - requires + requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), diff > 0, ensures spec.entails( @@ -1620,6 +1646,96 @@ pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( ) ), { + let pre = |s: VRSCluster| { + &&& req_msg_is_the_in_flight_delete_request_at_after_delete_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& exists_ok_resp_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff - 1)(s) + }; + let input = Some(req_msg); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_selector_matches_template_labels(vrs)(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.get_Some_0(); + // Case 1: We're processing the delete request + if msg == req_msg { + let resp_msg = VRSCluster::handle_delete_request_msg(req_msg, s.kubernetes_api_state).1; + let req = req_msg.content.get_delete_request(); + let key = req.key; + + // Asserts properties about the response message + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, msg) + &&& resp_msg.content.get_delete_response().res.is_Ok() + }); + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is decreased. + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + assert(matching_pod_entries(vrs, s.resources()).remove(key) == matching_pod_entries(vrs, s_prime.resources())); + } else { + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + lemma_api_request_not_on_matching_pods_maintains_matching_pods( + s, s_prime, vrs, diff, msg, req_msg, + ); + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + } + }, + _ => {} + } + } + + assert forall |s, s_prime: VRSCluster| pre(s) && #[trigger] stronger_next(s, s_prime) && VRSCluster::kubernetes_api_next().forward(input)(s, s_prime) implies post(s_prime) by { + let resp_msg = VRSCluster::handle_delete_request_msg(req_msg, s.kubernetes_api_state).1; + let req = req_msg.content.get_delete_request(); + let key = req.key; + + // Asserts properties about the response message + assert({ + &&& s_prime.in_flight().contains(resp_msg) + &&& Message::resp_msg_matches_req_msg(resp_msg, req_msg) + &&& resp_msg.content.get_delete_response().res.is_Ok() + }); + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is decreased. + a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); + assert(matching_pod_entries(vrs, s.resources()).remove(key) == matching_pod_entries(vrs, s_prime.resources())); + } + + VRSCluster::lemma_pre_leads_to_post_by_kubernetes_api( + spec, input, stronger_next, VRSCluster::handle_request(), pre, post + ); } #[verifier(external_body)] diff --git a/src/controller_examples/v_replica_set_controller/proof/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/predicate.rs index e16843ef1..2fe766735 100644 --- a/src/controller_examples/v_replica_set_controller/proof/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/predicate.rs @@ -323,7 +323,6 @@ pub open spec fn exists_ok_resp_in_flight_at_after_delete_pod_step( &&& msg.dst == HostId::ApiServer &&& msg.content.is_APIRequest() &&& request.is_DeleteRequest() - &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) &&& exists |resp_msg| { &&& #[trigger] s.in_flight().contains(resp_msg) &&& Message::resp_msg_matches_req_msg(resp_msg, msg) @@ -345,7 +344,6 @@ pub open spec fn resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step( &&& msg.dst == HostId::ApiServer &&& msg.content.is_APIRequest() &&& request.is_DeleteRequest() - &&& delete_constraint(vrs, request.get_DeleteRequest_0())(s) &&& s.in_flight().contains(resp_msg) &&& Message::resp_msg_matches_req_msg(resp_msg, msg) &&& resp_msg.content.get_delete_response().res.is_Ok() From a8589f6080756157961560bb30dc5b72556fdf79 Mon Sep 17 00:00:00 2001 From: Cody Rivera Date: Tue, 10 Sep 2024 22:15:56 -0500 Subject: [PATCH 21/22] Prove outline of final WF1 lemma Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 13 +- .../proof/liveness/api_actions.rs | 3 - .../proof/liveness/proof.rs | 2 +- .../proof/liveness/resource_match.rs | 131 +++++++++++++++++- .../proof/liveness/spec.rs | 6 +- 5 files changed, 146 insertions(+), 9 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 31a2b4d72..3935e7f8f 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -38,6 +38,14 @@ pub open spec fn vrs_replicas_bounded( } } +pub open spec fn matching_pods_bounded( + vrs: VReplicaSetView +) -> StatePred { + |s: VRSCluster| { + 0 <= matching_pod_entries(vrs, s.resources()).len() <= i32::MAX // As allowed by the previous invariant. + } +} + pub open spec fn vrs_selector_matches_template_labels( vrs: VReplicaSetView ) -> StatePred { @@ -152,8 +160,8 @@ pub open spec fn at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_ vrs: VReplicaSetView ) -> StatePred { |s: VRSCluster| { - forall |diff: usize| { - #[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff))(s) + forall |diff: nat| { + #[trigger] at_vrs_step_with_vrs(vrs, VReplicaSetReconcileStep::AfterDeletePod(diff as usize))(s) } ==> { let state = s.ongoing_reconciles()[vrs.object_ref()].local_state; let filtered_pods = state.filtered_pods.unwrap(); @@ -164,6 +172,7 @@ pub open spec fn at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_ &&& filtered_pod_keys.no_duplicates() &&& forall |i| #![auto] 0 <= i < diff ==> { &&& matching_pod_entries(vrs, s.resources()).contains_key(filtered_pod_keys[i]) + &&& matching_pod_entries(vrs, s.resources())[filtered_pod_keys[i]] == filtered_pods[i].marshal() } } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs index b85efccfd..1e739c759 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -29,9 +29,6 @@ pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_ ) requires VRSCluster::next_step(s, s_prime, Step::ApiServerStep(Some(msg))), - VRSCluster::crash_disabled()(s), - VRSCluster::busy_disabled()(s), - VRSCluster::every_in_flight_msg_has_unique_id()(s), VRSCluster::each_object_in_etcd_is_well_formed()(s), helper_invariants::every_create_request_is_well_formed()(s), helper_invariants::no_pending_update_or_update_status_request_on_pods()(s), diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs index c6d2f881b..5bfdeaecb 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/proof.rs @@ -67,6 +67,7 @@ proof fn liveness_proof(vrs: VReplicaSetView) simplify_predicate(cluster_spec(), derived_invariants_since_beginning(vrs)); } +#[verifier(external_body)] proof fn spec_before_phase_n_entails_true_leads_to_current_state_matches(i: nat, vrs: VReplicaSetView) requires 1 <= i <= 7, @@ -74,7 +75,6 @@ proof fn spec_before_phase_n_entails_true_leads_to_current_state_matches(i: nat, spec_before_phase_n(i + 1, vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))) ensures spec_before_phase_n(i, vrs).entails(true_pred().leads_to(always(lift_state(current_state_matches(vrs))))), { - assume(false); } proof fn lemma_true_leads_to_always_current_state_matches(vrs: VReplicaSetView) diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 4aef79c28..6fb4a0f33 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -41,11 +41,13 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)))), ensures spec.entails( lift_state( @@ -70,11 +72,13 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) + &&& spec.entails(always(lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)))) }; let pre = |diff: int| lift_state( |s: VRSCluster| { @@ -1377,17 +1381,23 @@ pub proof fn lemma_from_after_receive_delete_pod_resp_to_receive_delete_pod_resp ) requires spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))), spec.entails(always(lift_state(VRSCluster::crash_disabled()))), spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)))), diff > 0, ensures spec.entails( @@ -1738,11 +1748,26 @@ pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( ); } -#[verifier(external_body)] pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( spec: TempPred, vrs: VReplicaSetView, resp_msg: VRSMessage, diff: int ) requires + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))), + spec.entails(always(lift_state(VRSCluster::crash_disabled()))), + spec.entails(always(lift_state(VRSCluster::busy_disabled()))), + spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), + spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), + spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), + spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)))), diff > 0, ensures spec.entails( @@ -1761,18 +1786,122 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( ) ), { + let pre = |s: VRSCluster| { + &&& resp_msg_is_the_in_flight_ok_resp_at_after_delete_pod_step(vrs, resp_msg, abs(diff))(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let post = |s: VRSCluster| { + &&& pending_req_in_flight_at_after_delete_pod_step(vrs, (abs(diff) - 1) as nat)(s) + &&& num_diff_pods_is(vrs, diff)(s) + }; + let input = (Some(resp_msg), Some(vrs.object_ref())); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::crash_disabled()(s) + &&& VRSCluster::busy_disabled()(s) + &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())(s) + &&& helper_invariants::cluster_resources_is_finite()(s) + &&& helper_invariants::vrs_replicas_bounded(vrs)(s) + &&& helper_invariants::matching_pods_bounded(vrs)(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + &&& helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::crash_disabled()), + lift_state(VRSCluster::busy_disabled()), + lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())), + lift_state(helper_invariants::cluster_resources_is_finite()), + lift_state(helper_invariants::vrs_replicas_bounded(vrs)), + lift_state(helper_invariants::matching_pods_bounded(vrs)), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)), + lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)) + ); + + assert forall |s, s_prime| pre(s) && #[trigger] stronger_next(s, s_prime) implies pre(s_prime) || post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + // TODO: This could be sped up a bit by extra annotations, but it goes through automatically. + + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + _ => {} + } + } + + VRSCluster::lemma_pre_leads_to_post_by_controller( + spec, input, stronger_next, VRSCluster::continue_reconcile(), pre, post + ); } // Ensures that once we've reconciled, we stay reconciled. + +// This is actually trickier than I thought it'd be... #[verifier(external_body)] pub proof fn lemma_current_state_matches_is_stable( spec: TempPred, vrs: VReplicaSetView, p: TempPred ) requires spec.entails(p.leads_to(lift_state(current_state_matches(vrs)))), + spec.entails(always(lift_action(VRSCluster::next()))), + spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), + spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), + spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), + spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))), ensures spec.entails(p.leads_to(always(lift_state(current_state_matches(vrs))))), { + let post = current_state_matches(vrs); + let stronger_next = |s, s_prime: VRSCluster| { + &&& VRSCluster::next()(s, s_prime) + &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) + &&& helper_invariants::every_create_request_is_well_formed()(s) + &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) + &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) + &&& helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)(s) + }; + combine_spec_entails_always_n!( + spec, lift_action(stronger_next), + lift_action(VRSCluster::next()), + lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), + lift_state(helper_invariants::every_create_request_is_well_formed()), + lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), + lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), + lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)) + ); + + assert forall |s, s_prime: VRSCluster| post(s) && #[trigger] stronger_next(s, s_prime) implies post(s_prime) by { + let step = choose |step| VRSCluster::next_step(s, s_prime, step); + match step { + Step::ApiServerStep(input) => { + let msg = input.unwrap(); + lemma_api_request_outside_create_or_delete_loop_maintains_matching_pods( + s, s_prime, vrs, 0, msg + ); + // Small prod for the theorem prover to realize num_diff_pods_is(vrs, diff) is maintained. + assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); + }, + _ => {} + } + } + + leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); } } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index 5d1f4e0bd..dc68220d6 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -69,13 +69,13 @@ pub open spec fn spec_before_phase_n(n: nat, vrs: VReplicaSetView) -> TempPred valid(stable(#[trigger] spec_before_phase_n(i, vrs))), { - assume(false) } // Next and all the wf conditions. @@ -115,11 +115,13 @@ pub open spec fn derived_invariants_since_beginning(vrs: VReplicaSetView) -> Tem .and(always(tla_forall(|k : usize| lift_state(VRSCluster::pending_req_in_flight_or_resp_in_flight_at_reconcile_state(vrs.object_ref(), at_step_closure(VReplicaSetReconcileStep::AfterDeletePod(k))))))) .and(always(lift_state(helper_invariants::cluster_resources_is_finite()))) .and(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) + .and(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))) .and(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) .and(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) .and(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) .and(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) .and(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) + .and(always(lift_state(helper_invariants::at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries(vrs)))) } /// The first notable phase comes when crash and k8s busy are always disabled and the object in schedule always has the same @@ -158,10 +160,10 @@ pub open spec fn invariants_since_phase_vii(vrs: VReplicaSetView) -> TempPred Date: Wed, 11 Sep 2024 13:29:38 -0500 Subject: [PATCH 22/22] Cleanup and explain unproven assumptions Signed-off-by: Cody Rivera --- .../proof/helper_invariants/predicate.rs | 52 ++++++++++++- .../proof/liveness/api_actions.rs | 11 ++- .../proof/liveness/resource_match.rs | 76 ++++++------------- .../proof/liveness/spec.rs | 10 +++ src/vstd_ext/map_lib.rs | 6 +- src/vstd_ext/seq_lib.rs | 4 +- src/vstd_ext/set_lib.rs | 2 +- 7 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs index 3935e7f8f..5ba9463df 100644 --- a/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs +++ b/src/controller_examples/v_replica_set_controller/proof/helper_invariants/predicate.rs @@ -29,7 +29,6 @@ pub open spec fn cluster_resources_is_finite() -> StatePred { |s: VRSCluster| s.resources().dom().finite() } -// The proof will probabily involve more changes elsewhere. pub open spec fn vrs_replicas_bounded( vrs: VReplicaSetView ) -> StatePred { @@ -37,6 +36,11 @@ pub open spec fn vrs_replicas_bounded( 0 <= vrs.spec.replicas.unwrap_or(0) <= i32::MAX // As allowed by Kubernetes. } } +// +// TODO: Prove this. +// +// This should be easy to enforce with state validation. +// pub open spec fn matching_pods_bounded( vrs: VReplicaSetView @@ -45,6 +49,11 @@ pub open spec fn matching_pods_bounded( 0 <= matching_pod_entries(vrs, s.resources()).len() <= i32::MAX // As allowed by the previous invariant. } } +// +// TODO: Prove this. +// +// This should be easy to enforce with state validation. +// pub open spec fn vrs_selector_matches_template_labels( vrs: VReplicaSetView @@ -61,6 +70,11 @@ pub open spec fn vrs_selector_matches_template_labels( vrs.spec.selector.matches(match_value) } } +// +// TODO: Prove this. +// +// This should be easy to enforce with state validation. +// pub open spec fn every_create_request_is_well_formed() -> StatePred { |s: VRSCluster| { @@ -101,6 +115,12 @@ pub open spec fn every_create_request_is_well_formed() -> StatePred } } } +// +// TODO: Prove this. +// +// Proving this for the VReplicaSet controller should be easy; we'd need to do a similar +// proof for other state machines within the compound state machine. +// pub open spec fn no_pending_update_or_update_status_request_on_pods() -> StatePred { |s: VRSCluster| { @@ -114,7 +134,12 @@ pub open spec fn no_pending_update_or_update_status_request_on_pods() -> StatePr } } } - +// +// TODO: Prove this. +// +// Proving this for the VReplicaSet controller should be easy; we'd need to do a similar +// proof for other state machines within the compound state machine. +// pub open spec fn every_create_matching_pod_request_implies_at_after_create_pod_step( vrs: VReplicaSetView @@ -134,6 +159,12 @@ pub open spec fn every_create_matching_pod_request_implies_at_after_create_pod_s } } } +// +// TODO: Prove this. +// +// We know that if VReplicaSet sends a create matching pod request, that it's at an `AfterCreatePod` state. +// We show this for the other state machines by showing they don't create matching pods. +// pub open spec fn every_delete_matching_pod_request_implies_at_after_delete_pod_step( vrs: VReplicaSetView @@ -155,6 +186,11 @@ pub open spec fn every_delete_matching_pod_request_implies_at_after_delete_pod_s } } } +// +// TODO: Prove this. +// +// The proof sketch for this invariant is similar to the above. +// pub open spec fn at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_entries( vrs: VReplicaSetView @@ -177,5 +213,17 @@ pub open spec fn at_after_delete_pod_step_implies_filtered_pods_in_matching_pod_ } } } +// +// TODO: Prove this. +// +// We prove this by first showing that in the AfterListPods -> AfterDeletePod transition, that +// the `filtered_pods` variable contains matching pods in etcd. Next, we show that for +// AfterDeletePod(diff) => AfterDeletePod(diff - 1), that the pods `filtered_pods[i]`, for +// i = 1..diff - 2 are unaffected, since `filtered_pods[diff - 1]` is deleted, and the invariant +// will hold after `diff` is decreased. +// +// This invariant may have to be moved to a later phase, since I think this invariant will rely +// on other invariants. +// } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs index 1e739c759..6494ef968 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/api_actions.rs @@ -74,16 +74,13 @@ pub proof fn lemma_api_request_outside_create_or_delete_loop_maintains_matching_ } #[verifier(external_body)] -pub proof fn lemma_api_request_not_on_matching_pods_maintains_matching_pods( +pub proof fn lemma_api_request_not_made_by_vrs_maintains_matching_pods( s: VRSCluster, s_prime: VRSCluster, vrs: VReplicaSetView, diff: int, msg: VRSMessage, req_msg: VRSMessage, ) requires msg != req_msg, req_msg == s.ongoing_reconciles()[vrs.object_ref()].pending_req_msg.get_Some_0(), VRSCluster::next_step(s, s_prime, Step::ApiServerStep(Some(msg))), - VRSCluster::crash_disabled()(s), - VRSCluster::busy_disabled()(s), - VRSCluster::every_in_flight_msg_has_unique_id()(s), VRSCluster::each_object_in_etcd_is_well_formed()(s), helper_invariants::every_create_request_is_well_formed()(s), helper_invariants::no_pending_update_or_update_status_request_on_pods()(s), @@ -92,10 +89,12 @@ pub proof fn lemma_api_request_not_on_matching_pods_maintains_matching_pods( ensures matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources()); // -// @Xudong Sun take a look +// TODO: Prove this. // // This is very similar to the proof above, but we'll need to explicitly handle the case that we're on -// an after create or delete pod step. +// an after create or delete pod step. But fortunately, `every_create` and `every_delete` require that +// every create/delete matching pod request is VRS's pending request message, which clearly `msg` +// is not. // } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs index 6fb4a0f33..4c3af685d 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/resource_match.rs @@ -20,7 +20,10 @@ use crate::v_replica_set_controller::{ proof::{helper_invariants, predicate::*, liveness::api_actions::*}, trusted::{spec_types::*, step::*, liveness_theorem::*}, }; -use vstd::{prelude::*, seq::*, seq_lib::*, set_lib::*, string::*, map::*, map_lib::*, math::abs}; +use vstd::{ + prelude::*, seq::*, seq_lib::*, set_lib::*, + string::*, map::*, map_lib::*, math::abs +}; verus! { @@ -37,8 +40,6 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))), spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))), - spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( - vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))), spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))), spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))), @@ -68,8 +69,6 @@ pub proof fn lemma_from_diff_and_init_to_current_state_matches( &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) - &&& spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( - vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))) &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) &&& spec.entails(always(lift_state(helper_invariants::matching_pods_bounded(vrs)))) @@ -302,8 +301,6 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( spec.entails(always(lift_state(VRSCluster::busy_disabled()))), spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))), spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))), - spec.entails(always(lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state( - vrs.object_ref(), |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)))), spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))), spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))), spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))), @@ -340,8 +337,6 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( &&& VRSCluster::busy_disabled()(s) &&& VRSCluster::every_in_flight_msg_has_unique_id()(s) &&& VRSCluster::each_object_in_etcd_is_well_formed()(s) - &&& VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), - |s: VReplicaSetReconcileState| s.reconcile_step == VReplicaSetReconcileStep::Init)(s) &&& helper_invariants::every_create_request_is_well_formed()(s) &&& helper_invariants::no_pending_update_or_update_status_request_on_pods()(s) &&& helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)(s) @@ -355,7 +350,6 @@ pub proof fn lemma_from_init_step_to_send_list_pods_req( lift_state(VRSCluster::busy_disabled()), lift_state(VRSCluster::every_in_flight_msg_has_unique_id()), lift_state(VRSCluster::each_object_in_etcd_is_well_formed()), - lift_state(VRSCluster::no_pending_req_msg_at_reconcile_state(vrs.object_ref(), state_is_init)), lift_state(helper_invariants::every_create_request_is_well_formed()), lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()), lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)), @@ -604,24 +598,6 @@ pub proof fn lemma_from_after_receive_list_pods_resp_to_receive_create_pod_resp( ) ), { - let invariants = { - &&& spec.entails(always(lift_action(VRSCluster::next()))) - &&& spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))) - &&& spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))) - &&& spec.entails(always(lift_state(VRSCluster::crash_disabled()))) - &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) - &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) - &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) - &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) - &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) - &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) - &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) - &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) - }; let list_resp = |diff: int| lift_state( |s: VRSCluster| { &&& exists_resp_in_flight_at_after_list_pods_step(vrs)(s) @@ -754,24 +730,6 @@ pub proof fn lemma_from_after_receive_create_pod_resp_to_receive_create_pod_resp ) ), { - let invariants = { - &&& spec.entails(always(lift_action(VRSCluster::next()))) - &&& spec.entails(tla_forall(|i| VRSCluster::controller_next().weak_fairness(i))) - &&& spec.entails(tla_forall(|i| VRSCluster::kubernetes_api_next().weak_fairness(i))) - &&& spec.entails(always(lift_state(VRSCluster::crash_disabled()))) - &&& spec.entails(always(lift_state(VRSCluster::busy_disabled()))) - &&& spec.entails(always(lift_state(VRSCluster::every_in_flight_msg_has_unique_id()))) - &&& spec.entails(always(lift_state(VRSCluster::each_object_in_etcd_is_well_formed()))) - &&& spec.entails(always(lift_state(VRSCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()))) - &&& spec.entails(always(lift_state(VRSCluster::pending_req_of_key_is_unique_with_unique_id(vrs.object_ref())))) - &&& spec.entails(always(lift_state(helper_invariants::cluster_resources_is_finite()))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_replicas_bounded(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::vrs_selector_matches_template_labels(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::every_create_request_is_well_formed()))) - &&& spec.entails(always(lift_state(helper_invariants::no_pending_update_or_update_status_request_on_pods()))) - &&& spec.entails(always(lift_state(helper_invariants::every_create_matching_pod_request_implies_at_after_create_pod_step(vrs)))) - &&& spec.entails(always(lift_state(helper_invariants::every_delete_matching_pod_request_implies_at_after_delete_pod_step(vrs)))) - }; let create_req_msg = |req_msg: VRSMessage, diff: int| lift_state(|s: VRSCluster| { &&& req_msg_is_the_in_flight_create_request_at_after_create_pod_step(vrs, req_msg, (abs(diff) - 1) as nat)(s) &&& num_diff_pods_is(vrs, diff)(s) @@ -963,11 +921,11 @@ pub proof fn lemma_filtered_pods_len_equals_matching_pods_len( }), {} // -// @Xudong Sun take a look. +// TODO: Prove this. // // filter_pods filters pods in a very similar way to matching_pods, but the former // works on a sequence of PodViews, while the latter works on the key-value store directly. -// I'll probably come back to this soon. +// I need to do some manual effort to make the two representations work together. // @@ -1091,7 +1049,7 @@ pub proof fn lemma_from_after_send_create_pod_req_to_receive_ok_resp( assert(matching_pod_entries(vrs, s.resources()).insert(key, created_obj) == matching_pod_entries(vrs, s_prime.resources())); } else { a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); - lemma_api_request_not_on_matching_pods_maintains_matching_pods( + lemma_api_request_not_made_by_vrs_maintains_matching_pods( s, s_prime, vrs, diff, msg, req_msg, ); assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); } @@ -1618,7 +1576,14 @@ pub proof fn lemma_filtered_pods_set_equals_matching_pods( }), {} // -// This might actually be slightly trickier than the previous one. +// TODO: Prove this. +// +// filter_pods filters pods in a very similar way to matching_pods, but the former +// works on a sequence of PodViews, while the latter works on the key-value store directly. +// I need to do some manual effort to make the two representations work together. +// +// Once proven, this will supplant the earlier lemma +// lemma_filtered_pods_len_equals_matching_pods_len. // @@ -1716,7 +1681,7 @@ pub proof fn lemma_from_after_send_delete_pod_req_to_receive_ok_resp( assert(matching_pod_entries(vrs, s.resources()).remove(key) == matching_pod_entries(vrs, s_prime.resources())); } else { a_submap_of_a_finite_map_is_finite(matching_pod_entries(vrs, s.resources()), s.resources()); - lemma_api_request_not_on_matching_pods_maintains_matching_pods( + lemma_api_request_not_made_by_vrs_maintains_matching_pods( s, s_prime, vrs, diff, msg, req_msg, ); assert(matching_pod_entries(vrs, s.resources()) == matching_pod_entries(vrs, s_prime.resources())); @@ -1849,9 +1814,6 @@ pub proof fn lemma_from_after_receive_ok_resp_to_send_delete_pod_req( ); } -// Ensures that once we've reconciled, we stay reconciled. - -// This is actually trickier than I thought it'd be... #[verifier(external_body)] pub proof fn lemma_current_state_matches_is_stable( spec: TempPred, vrs: VReplicaSetView, p: TempPred @@ -1903,5 +1865,11 @@ pub proof fn lemma_current_state_matches_is_stable( leads_to_stable_temp(spec, lift_action(stronger_next), p, lift_state(post)); } +// +// TODO: Prove this. +// +// The earlier controllers only created resources and never deleted them, so stability of reconciliation +// was trivial, while it seems more difficult here since the controller can delete resources. +// } diff --git a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs index dc68220d6..868ed8d5a 100644 --- a/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs +++ b/src/controller_examples/v_replica_set_controller/proof/liveness/spec.rs @@ -77,6 +77,11 @@ pub proof fn assumption_and_invariants_of_all_phases_is_stable(vrs: VReplicaSetV forall |i: nat| 1 <= i <= 8 ==> valid(stable(#[trigger] spec_before_phase_n(i, vrs))), { } +// +// TODO: Prove this. +// +// Main body for showing invariant stability. +// // Next and all the wf conditions. pub open spec fn next_with_wf() -> TempPred { @@ -165,5 +170,10 @@ pub proof fn sm_spec_entails_all_invariants(vrs: VReplicaSetView) ensures cluster_spec().entails(derived_invariants_since_beginning(vrs)), { } +// +// TODO: Prove this. +// +// Main body for showing that invariants hold. +// } diff --git a/src/vstd_ext/map_lib.rs b/src/vstd_ext/map_lib.rs index 755f3c991..2a2bb3acd 100644 --- a/src/vstd_ext/map_lib.rs +++ b/src/vstd_ext/map_lib.rs @@ -17,9 +17,9 @@ pub proof fn a_submap_of_a_finite_map_is_finite(m1: Map, m2: Map(s: Seq, p requires forall |e: A| #![auto] s.contains(e) ==> !pred(e), ensures s.filter(pred).len() == 0; // -// @Xudong Sun take a look. -// +// TODO: Prove this -- Trivial. +// // If `pred` is false on every element, filter will return an empty sequence. // diff --git a/src/vstd_ext/set_lib.rs b/src/vstd_ext/set_lib.rs index 229148ff2..71eb39413 100644 --- a/src/vstd_ext/set_lib.rs +++ b/src/vstd_ext/set_lib.rs @@ -12,7 +12,7 @@ pub proof fn finite_set_to_seq_contains_all_set_elements(s: Set) requires s.finite(), ensures forall |e: A| #![auto] s.contains(e) <==> s.to_seq().contains(e); // -// @Xudong Sun take a look. +// TODO: Prove this -- Trivial. // // Anything in a finite set will be in a sequence composed of its elements; likewise // anything in that constructed sequence will be part of the original set.