From c94a472f369c910e4213415bcec1abebcc171b6a Mon Sep 17 00:00:00 2001 From: Wenjie Ma Date: Fri, 25 Aug 2023 22:51:54 -0500 Subject: [PATCH] Refine and refactor the proof and file structure Compact some similar lemmas. Try to delete some useless functions. Move some lemmas to common module (i.e, kubernetes_cluster::proof, not specific to one controller). Refactor the file structure to make it more reasonable. Reword some comments and naming. Try to make the verification more stable. Rename lower_rv => smaller_rv. --------- Signed-off-by: Wenjie Ma --- src/2023-08-23-21-12-34.zip | Bin 173075 -> 0 bytes .../rabbitmq_controller/proof/common.rs | 9 +- .../helper_invariants/invariants.rs | 120 ++ .../{liveness => }/helper_invariants/mod.rs | 0 .../proof/helper_invariants/owner_ref.rs | 225 ++++ .../liveness/helper_invariants/owner_ref.rs | 99 -- .../proof/liveness/liveness.rs | 4 +- .../rabbitmq_controller/proof/liveness/mod.rs | 1 - .../rabbitmq_controller/proof/mod.rs | 1 + .../rabbitmq_controller/proof/safety.rs | 1069 ----------------- .../rabbitmq_controller/proof/safety/mod.rs | 3 + .../proof/safety/safety.rs | 557 +++++++++ src/kubernetes_api_objects/resource.rs | 1 + .../proof/builtin_controllers.rs | 6 + src/kubernetes_cluster/proof/message.rs | 115 ++ src/kubernetes_cluster/proof/mod.rs | 1 + .../proof/validation_rule.rs | 263 ++++ 17 files changed, 1295 insertions(+), 1179 deletions(-) delete mode 100644 src/2023-08-23-21-12-34.zip rename src/controller_examples/rabbitmq_controller/proof/{liveness => }/helper_invariants/invariants.rs (89%) rename src/controller_examples/rabbitmq_controller/proof/{liveness => }/helper_invariants/mod.rs (100%) create mode 100644 src/controller_examples/rabbitmq_controller/proof/helper_invariants/owner_ref.rs delete mode 100644 src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/owner_ref.rs delete mode 100644 src/controller_examples/rabbitmq_controller/proof/safety.rs create mode 100644 src/controller_examples/rabbitmq_controller/proof/safety/mod.rs create mode 100644 src/controller_examples/rabbitmq_controller/proof/safety/safety.rs create mode 100644 src/kubernetes_cluster/proof/validation_rule.rs diff --git a/src/2023-08-23-21-12-34.zip b/src/2023-08-23-21-12-34.zip deleted file mode 100644 index cc0eff11d1f3243454495c434d525ab68ffc0beb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173075 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�N(g%PW3)go6d^-m*(= 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 FnSpec(RMQMessage) -> msg.dst.is_KubernetesAPI() && msg.content.is_update_request() && msg.content.get_update_request().key == make_stateful_set_key(key) + && msg.content.get_update_request().obj.kind == Kind::StatefulSetKind } pub open spec fn sts_get_response_msg(key: ObjectRef) -> FnSpec(RMQMessage) -> bool { @@ -394,12 +395,4 @@ pub open spec fn sts_get_response_msg(key: ObjectRef) -> FnSpec(RMQMessage) -> b ) } -pub open spec fn ok_sts_get_response_msg(key: ObjectRef) -> FnSpec(RMQMessage) -> bool { - |msg: RMQMessage| - msg.src.is_KubernetesAPI() - && msg.content.is_get_response() - && msg.content.get_get_response().res.is_Ok() - && msg.content.get_get_response().res.get_Ok_0().object_ref() == make_stateful_set_key(key) -} - } diff --git a/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/invariants.rs b/src/controller_examples/rabbitmq_controller/proof/helper_invariants/invariants.rs similarity index 89% rename from src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/invariants.rs rename to src/controller_examples/rabbitmq_controller/proof/helper_invariants/invariants.rs index 886d12562..80257f726 100644 --- a/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/invariants.rs +++ b/src/controller_examples/rabbitmq_controller/proof/helper_invariants/invariants.rs @@ -1096,4 +1096,124 @@ pub proof fn lemma_true_leads_to_always_no_delete_sts_req_is_in_flight(spec: Tem ); } +/// This spec tells that when the reconciler is at AfterGetStatefulSet, and there is a matched response, the reponse must be +/// sts_get_response_msg. This lemma is used to show that the response message, if is ok, has an object whose reference is +/// stateful_set_key. resp_msg_matches_req_msg doesn't talk about the object in response should match the key in request +/// so we need this extra spec and lemma. +/// +/// If we don't have this, we have no idea of what is inside the response message. +pub open spec fn response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq: RabbitmqClusterView) -> StatePred { + let key = rabbitmq.object_ref(); + |s: RMQCluster| { + at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s) + ==> s.reconcile_state_of(key).pending_req_msg.is_Some() + && is_get_stateful_set_request(s.pending_req_of(key).content.get_APIRequest_0(), rabbitmq) + && ( + forall |msg: RMQMessage| + #[trigger] s.message_in_flight(msg) + && Message::resp_msg_matches_req_msg(msg, s.pending_req_of(key)) + ==> sts_get_response_msg(key)(msg) + ) + } +} + +pub proof fn lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)))), +{ + let inv = response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq); + let key = rabbitmq.object_ref(); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::every_in_flight_msg_has_lower_id_than_allocator()(s) + &&& RMQCluster::every_in_flight_or_pending_req_msg_has_unique_id()(s) + &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + }; + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + RMQCluster::lemma_always_every_in_flight_msg_has_lower_id_than_allocator(spec); + RMQCluster::lemma_always_every_in_flight_or_pending_req_msg_has_unique_id(spec); + RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), + lift_state(RMQCluster::every_in_flight_msg_has_lower_id_than_allocator()), + lift_state(RMQCluster::every_in_flight_or_pending_req_msg_has_unique_id()), + lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + if at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s_prime) { + if at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s) { + assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); + assert(s_prime.reconcile_state_of(key).pending_req_msg.is_Some()); + assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); + } else { + assert(at_rabbitmq_step(key, RabbitmqReconcileStep::AfterCreateRoleBinding)(s)); + assert(s_prime.reconcile_state_of(key).pending_req_msg.is_Some()); + assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); + } + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && Message::resp_msg_matches_req_msg(msg, s_prime.pending_req_of(key)) + implies sts_get_response_msg(key)(msg) by { + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + match step { + Step::ControllerStep(input) => { + assert(s.message_in_flight(msg)); + let cr_key = input.1.get_Some_0(); + if cr_key == key { + assert(s.message_in_flight(msg)); + assert(false); + } else { + assert(s.pending_req_of(key) == s_prime.pending_req_of(key)); + assert(sts_get_response_msg(key)(msg)); + } + }, + Step::KubernetesAPIStep(input) => { + assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); + if !s.message_in_flight(msg) { + assert(RMQCluster::in_flight_or_pending_req_message(s, s.pending_req_of(key))); + assert(RMQCluster::in_flight_or_pending_req_message(s, input.get_Some_0())); + assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); + assert(msg.content.is_get_response()); + assert(msg == RMQCluster::handle_get_request(s.pending_req_of(key), s.kubernetes_api_state).1); + assert(msg.src.is_KubernetesAPI() + && msg.content.is_get_response()); + if msg.content.get_get_response().res.is_Ok() { + assert(s.resource_key_exists(make_stateful_set_key(key))); + assert(s.resource_obj_of(make_stateful_set_key(key)).object_ref() == make_stateful_set_key(key)); + } + assert(sts_get_response_msg(key)(msg)); + } + }, + Step::KubernetesBusy(input) => { + assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); + if !s.message_in_flight(msg) { + assert(RMQCluster::in_flight_or_pending_req_message(s, s.pending_req_of(key))); + assert(RMQCluster::in_flight_or_pending_req_message(s, input.get_Some_0())); + assert(msg.src.is_KubernetesAPI()); + assert(msg.content.is_get_response()); + assert(msg.content.get_get_response().res.is_Err()); + } + assert(sts_get_response_msg(key)(msg)); + }, + Step::ClientStep() => { + assert(s.message_in_flight(msg)); + assert(sts_get_response_msg(key)(msg)); + }, + Step::ExternalAPIStep(input) => { + assert(input.get_Some_0() != msg); + assert(s.message_in_flight(msg)); + }, + _ => { + assert(s.message_in_flight(msg)); + assert(sts_get_response_msg(key)(msg)); + } + } + } + } + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + } diff --git a/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/mod.rs b/src/controller_examples/rabbitmq_controller/proof/helper_invariants/mod.rs similarity index 100% rename from src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/mod.rs rename to src/controller_examples/rabbitmq_controller/proof/helper_invariants/mod.rs diff --git a/src/controller_examples/rabbitmq_controller/proof/helper_invariants/owner_ref.rs b/src/controller_examples/rabbitmq_controller/proof/helper_invariants/owner_ref.rs new file mode 100644 index 000000000..6c0defccf --- /dev/null +++ b/src/controller_examples/rabbitmq_controller/proof/helper_invariants/owner_ref.rs @@ -0,0 +1,225 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use super::invariants::*; +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::{ + 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::common::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::rabbitmq_controller::{ + common::*, + proof::common::*, + spec::{rabbitmqcluster::*, reconciler::*}, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use vstd::prelude::*; + +verus! { + +pub proof fn lemma_eventually_only_valid_server_config_map_exists(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + rabbitmq.well_formed(), + spec.entails(always(lift_state(RMQCluster::busy_disabled()))), + spec.entails(always(lift_action(RMQCluster::next()))), + spec.entails(tla_forall(|i| RMQCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| RMQCluster::builtin_controllers_next().weak_fairness(i))), + spec.entails(always(lift_state(RMQCluster::desired_state_is(rabbitmq)))), + spec.entails(always(lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)))), + spec.entails(always(lift_state(every_update_server_cm_req_does_the_same(rabbitmq)))), + spec.entails(always(lift_state(every_create_server_cm_req_does_the_same(rabbitmq)))), + ensures + spec.entails(true_pred().leads_to(always(lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq))))), +{ + let key = make_server_config_map_key(rabbitmq.object_ref()); + let eventual_owner_ref = |owner_ref: Option>| {owner_ref == Some(seq![rabbitmq.controller_owner_ref()])}; + always_weaken(spec, every_update_server_cm_req_does_the_same(rabbitmq), RMQCluster::every_update_msg_sets_owner_references_as(key, eventual_owner_ref)); + always_weaken(spec, every_create_server_cm_req_does_the_same(rabbitmq), RMQCluster::every_create_msg_sets_owner_references_as(key, eventual_owner_ref)); + always_weaken(spec, object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq), RMQCluster::object_has_no_finalizers(key)); + + let state = |s: RMQCluster| { + RMQCluster::desired_state_is(rabbitmq)(s) + && object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)(s) + }; + invariant_n!( + spec, lift_state(state), lift_state(RMQCluster::objects_owner_references_violates(key, eventual_owner_ref)).implies(lift_state(RMQCluster::garbage_collector_deletion_enabled(key))), + lift_state(RMQCluster::desired_state_is(rabbitmq)), + lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)) + ); + RMQCluster::lemma_eventually_objects_owner_references_satisfies(spec, key, eventual_owner_ref); + temp_pred_equality( + lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)), + lift_state(RMQCluster::objects_owner_references_satisfies(key, eventual_owner_ref)) + ); +} + +pub proof fn lemma_eventually_only_valid_stateful_set_exists(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + rabbitmq.well_formed(), + spec.entails(always(lift_state(RMQCluster::busy_disabled()))), + spec.entails(always(lift_action(RMQCluster::next()))), + spec.entails(tla_forall(|i| RMQCluster::kubernetes_api_next().weak_fairness(i))), + spec.entails(tla_forall(|i| RMQCluster::builtin_controllers_next().weak_fairness(i))), + spec.entails(always(lift_state(RMQCluster::desired_state_is(rabbitmq)))), + spec.entails(always(lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)))), + spec.entails(always(lift_state(every_update_sts_req_does_the_same(rabbitmq)))), + spec.entails(always(lift_state(every_create_sts_req_does_the_same(rabbitmq)))), + ensures + spec.entails(true_pred().leads_to(always(lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq))))), +{ + let key = make_stateful_set_key(rabbitmq.object_ref()); + let eventual_owner_ref = |owner_ref: Option>| {owner_ref == Some(seq![rabbitmq.controller_owner_ref()])}; + always_weaken(spec, every_update_sts_req_does_the_same(rabbitmq), RMQCluster::every_update_msg_sets_owner_references_as(key, eventual_owner_ref)); + always_weaken(spec, every_create_sts_req_does_the_same(rabbitmq), RMQCluster::every_create_msg_sets_owner_references_as(key, eventual_owner_ref)); + always_weaken(spec, object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq), RMQCluster::object_has_no_finalizers(key)); + + let state = |s: RMQCluster| { + RMQCluster::desired_state_is(rabbitmq)(s) + && object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)(s) + }; + invariant_n!( + spec, lift_state(state), lift_state(RMQCluster::objects_owner_references_violates(key, eventual_owner_ref)).implies(lift_state(RMQCluster::garbage_collector_deletion_enabled(key))), + lift_state(RMQCluster::desired_state_is(rabbitmq)), + lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)) + ); + RMQCluster::lemma_eventually_objects_owner_references_satisfies(spec, key, eventual_owner_ref); + temp_pred_equality( + lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)), + lift_state(RMQCluster::objects_owner_references_satisfies(key, eventual_owner_ref)) + ); +} + +/// Valid owner_references field satisfies that every owner reference in it valid uid, i.e., it points to some existing objects. +/// We don't test custom resource object here because we don't care about whether it's owner_references is valid. +pub open spec fn owner_references_is_valid(obj: DynamicObjectView, s: RMQCluster) -> bool { + if obj.kind.is_CustomResourceKind() { + true + } else { + let owner_refs = obj.metadata.owner_references.get_Some_0(); + forall |i| + #![auto] 0 <= i < owner_refs.len() + ==> owner_refs[i].uid < s.kubernetes_api_state.uid_counter + } +} + +pub open spec fn object_in_every_create_or_update_request_msg_only_has_valid_owner_references() -> StatePred { + |s: RMQCluster| { + forall |msg| + #[trigger] s.message_in_flight(msg) + && msg.dst.is_KubernetesAPI() + && msg.content.is_APIRequest() + ==> ( + msg.content.is_create_request() && msg.content.get_create_request().obj.metadata.owner_references.is_Some() + ==> owner_references_is_valid(msg.content.get_create_request().obj, s) + ) && ( + msg.content.is_update_request() && msg.content.get_update_request().obj.metadata.owner_references.is_Some() + ==> owner_references_is_valid(msg.content.get_update_request().obj, s) + ) + } +} + +pub proof fn lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec: TempPred) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references()))), +{ + let inv = object_in_every_create_or_update_request_msg_only_has_valid_owner_references(); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()(s) + }; + RMQCluster::lemma_always_triggering_cr_has_lower_uid_than_uid_counter(spec); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()) + ); + let create_valid = |msg: RMQMessage, s: RMQCluster| { + msg.content.is_create_request() && msg.content.get_create_request().obj.metadata.owner_references.is_Some() + ==> owner_references_is_valid(msg.content.get_create_request().obj, s) + }; + let update_valid = |msg: RMQMessage, s: RMQCluster| { + msg.content.is_update_request() && msg.content.get_update_request().obj.metadata.owner_references.is_Some() + ==> owner_references_is_valid(msg.content.get_update_request().obj, s) + }; + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && msg.dst.is_KubernetesAPI() && msg.content.is_APIRequest() + implies create_valid(msg, s_prime) && update_valid(msg, s_prime) by { + assert(s.kubernetes_api_state.uid_counter <= s_prime.kubernetes_api_state.uid_counter); + if !s.message_in_flight(msg) { + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + match step { + Step::ControllerStep(input) => { + let cr = s.triggering_cr_of(input.1.get_Some_0()); + if msg.content.is_create_request() { + let owner_refs = msg.content.get_create_request().obj.metadata.owner_references; + if owner_refs.is_Some() { + assert(owner_refs == Some(seq![cr.controller_owner_ref()])); + } + } else if msg.content.is_update_request() { + let owner_refs = msg.content.get_update_request().obj.metadata.owner_references; + if owner_refs.is_Some() { + assert(owner_refs == Some(seq![cr.controller_owner_ref()])); + } + } + }, + Step::ClientStep() => { + if msg.content.is_create_request() { + assert(msg.content.get_create_request().obj.kind.is_CustomResourceKind()); + } else if msg.content.is_update_request() { + assert(msg.content.get_update_request().obj.kind.is_CustomResourceKind()); + } + }, + Step::BuiltinControllersStep(_) => { + assert(!msg.content.is_update_request() && !msg.content.is_create_request()); + }, + _ => {} + } + } + } + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + +pub open spec fn every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter() -> StatePred { + |s: RMQCluster| { + forall |key| + #[trigger] s.resource_key_exists(key) + && s.resource_obj_of(key).metadata.owner_references.is_Some() + ==> owner_references_is_valid(s.resource_obj_of(key), s) + } +} + +pub proof fn lemma_always_every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(spec: TempPred) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()))), +{ + let inv = every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s) + }; + lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec); + combine_spec_entails_always_n!(spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references())); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + assert forall |key| + #[trigger] s_prime.resource_key_exists(key) && s_prime.resource_obj_of(key).metadata.owner_references.is_Some() + implies owner_references_is_valid(s_prime.resource_obj_of(key), s_prime) by { + assert(s.kubernetes_api_state.uid_counter <= s_prime.kubernetes_api_state.uid_counter); + if !s.resource_key_exists(key) || s.resource_obj_of(key).metadata.owner_references != s_prime.resource_obj_of(key).metadata.owner_references {} else {} + } + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + +} diff --git a/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/owner_ref.rs b/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/owner_ref.rs deleted file mode 100644 index 4aeb8fe8d..000000000 --- a/src/controller_examples/rabbitmq_controller/proof/liveness/helper_invariants/owner_ref.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use super::invariants::*; -use crate::external_api::spec::*; -use crate::kubernetes_api_objects::{ - 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::common::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::rabbitmq_controller::{ - common::*, - proof::common::*, - spec::{rabbitmqcluster::*, reconciler::*}, -}; -use crate::temporal_logic::{defs::*, rules::*}; -use vstd::prelude::*; - -verus! { - -pub proof fn lemma_eventually_only_valid_server_config_map_exists(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - rabbitmq.well_formed(), - spec.entails(always(lift_state(RMQCluster::busy_disabled()))), - spec.entails(always(lift_action(RMQCluster::next()))), - spec.entails(tla_forall(|i| RMQCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| RMQCluster::builtin_controllers_next().weak_fairness(i))), - spec.entails(always(lift_state(RMQCluster::desired_state_is(rabbitmq)))), - spec.entails(always(lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)))), - spec.entails(always(lift_state(every_update_server_cm_req_does_the_same(rabbitmq)))), - spec.entails(always(lift_state(every_create_server_cm_req_does_the_same(rabbitmq)))), - ensures - spec.entails(true_pred().leads_to(always(lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq))))), -{ - let key = make_server_config_map_key(rabbitmq.object_ref()); - let eventual_owner_ref = |owner_ref: Option>| {owner_ref == Some(seq![rabbitmq.controller_owner_ref()])}; - always_weaken(spec, every_update_server_cm_req_does_the_same(rabbitmq), RMQCluster::every_update_msg_sets_owner_references_as(key, eventual_owner_ref)); - always_weaken(spec, every_create_server_cm_req_does_the_same(rabbitmq), RMQCluster::every_create_msg_sets_owner_references_as(key, eventual_owner_ref)); - always_weaken(spec, object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq), RMQCluster::object_has_no_finalizers(key)); - - let state = |s: RMQCluster| { - RMQCluster::desired_state_is(rabbitmq)(s) - && object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)(s) - }; - invariant_n!( - spec, lift_state(state), lift_state(RMQCluster::objects_owner_references_violates(key, eventual_owner_ref)).implies(lift_state(RMQCluster::garbage_collector_deletion_enabled(key))), - lift_state(RMQCluster::desired_state_is(rabbitmq)), - lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)) - ); - RMQCluster::lemma_eventually_objects_owner_references_satisfies(spec, key, eventual_owner_ref); - temp_pred_equality( - lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_server_config_map_key(rabbitmq.object_ref()), rabbitmq)), - lift_state(RMQCluster::objects_owner_references_satisfies(key, eventual_owner_ref)) - ); -} - -pub proof fn lemma_eventually_only_valid_stateful_set_exists(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - rabbitmq.well_formed(), - spec.entails(always(lift_state(RMQCluster::busy_disabled()))), - spec.entails(always(lift_action(RMQCluster::next()))), - spec.entails(tla_forall(|i| RMQCluster::kubernetes_api_next().weak_fairness(i))), - spec.entails(tla_forall(|i| RMQCluster::builtin_controllers_next().weak_fairness(i))), - spec.entails(always(lift_state(RMQCluster::desired_state_is(rabbitmq)))), - spec.entails(always(lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)))), - spec.entails(always(lift_state(every_update_sts_req_does_the_same(rabbitmq)))), - spec.entails(always(lift_state(every_create_sts_req_does_the_same(rabbitmq)))), - ensures - spec.entails(true_pred().leads_to(always(lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq))))), -{ - let key = make_stateful_set_key(rabbitmq.object_ref()); - let eventual_owner_ref = |owner_ref: Option>| {owner_ref == Some(seq![rabbitmq.controller_owner_ref()])}; - always_weaken(spec, every_update_sts_req_does_the_same(rabbitmq), RMQCluster::every_update_msg_sets_owner_references_as(key, eventual_owner_ref)); - always_weaken(spec, every_create_sts_req_does_the_same(rabbitmq), RMQCluster::every_create_msg_sets_owner_references_as(key, eventual_owner_ref)); - always_weaken(spec, object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq), RMQCluster::object_has_no_finalizers(key)); - - let state = |s: RMQCluster| { - RMQCluster::desired_state_is(rabbitmq)(s) - && object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)(s) - }; - invariant_n!( - spec, lift_state(state), lift_state(RMQCluster::objects_owner_references_violates(key, eventual_owner_ref)).implies(lift_state(RMQCluster::garbage_collector_deletion_enabled(key))), - lift_state(RMQCluster::desired_state_is(rabbitmq)), - lift_state(object_of_key_has_no_finalizers_or_timestamp_and_only_has_controller_owner_ref(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)) - ); - RMQCluster::lemma_eventually_objects_owner_references_satisfies(spec, key, eventual_owner_ref); - temp_pred_equality( - lift_state(object_of_key_only_has_owner_reference_pointing_to_current_cr(make_stateful_set_key(rabbitmq.object_ref()), rabbitmq)), - lift_state(RMQCluster::objects_owner_references_satisfies(key, eventual_owner_ref)) - ); -} - -} diff --git a/src/controller_examples/rabbitmq_controller/proof/liveness/liveness.rs b/src/controller_examples/rabbitmq_controller/proof/liveness/liveness.rs index 1119e06fe..9c6475e9e 100644 --- a/src/controller_examples/rabbitmq_controller/proof/liveness/liveness.rs +++ b/src/controller_examples/rabbitmq_controller/proof/liveness/liveness.rs @@ -1,7 +1,7 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: MIT #![allow(unused_imports)] -use super::{helper_invariants, terminate}; +use super::terminate; use crate::external_api::spec::*; use crate::kubernetes_api_objects::{ api_method::*, common::*, config_map::*, dynamic::*, owner_reference::*, resource::*, @@ -16,7 +16,7 @@ use crate::kubernetes_cluster::spec::{ }; use crate::rabbitmq_controller::{ common::*, - proof::common::*, + proof::{common::*, helper_invariants}, spec::{rabbitmqcluster::*, reconciler::*}, }; use crate::temporal_logic::{defs::*, rules::*}; diff --git a/src/controller_examples/rabbitmq_controller/proof/liveness/mod.rs b/src/controller_examples/rabbitmq_controller/proof/liveness/mod.rs index 59f4e85f3..17b446f05 100644 --- a/src/controller_examples/rabbitmq_controller/proof/liveness/mod.rs +++ b/src/controller_examples/rabbitmq_controller/proof/liveness/mod.rs @@ -1,5 +1,4 @@ // Copyright 2022 VMware, Inc. // SPDX-License-Identifier: MIT pub mod liveness; -pub mod helper_invariants; pub mod terminate; \ No newline at end of file diff --git a/src/controller_examples/rabbitmq_controller/proof/mod.rs b/src/controller_examples/rabbitmq_controller/proof/mod.rs index 9c00b66a2..80edb80d6 100644 --- a/src/controller_examples/rabbitmq_controller/proof/mod.rs +++ b/src/controller_examples/rabbitmq_controller/proof/mod.rs @@ -3,3 +3,4 @@ pub mod common; pub mod liveness; pub mod safety; +pub mod helper_invariants; diff --git a/src/controller_examples/rabbitmq_controller/proof/safety.rs b/src/controller_examples/rabbitmq_controller/proof/safety.rs deleted file mode 100644 index f22374209..000000000 --- a/src/controller_examples/rabbitmq_controller/proof/safety.rs +++ /dev/null @@ -1,1069 +0,0 @@ -// Copyright 2022 VMware, Inc. -// SPDX-License-Identifier: MIT -#![allow(unused_imports)] -use crate::external_api::spec::*; -use crate::kubernetes_api_objects::{ - api_method::*, common::*, dynamic::*, resource::*, stateful_set::*, -}; -use crate::kubernetes_cluster::spec::{ - cluster::*, - cluster_state_machine::Step, - controller::common::{ControllerActionInput, ControllerStep}, - message::*, -}; -use crate::rabbitmq_controller::{ - common::*, - proof::{common::*, liveness::helper_invariants::invariants::*}, - spec::{rabbitmqcluster::*, reconciler::*}, -}; -use crate::temporal_logic::{defs::*, rules::*}; -use vstd::prelude::*; - -verus! { - -spec fn stateful_set_not_scaled_down(rabbitmq: RabbitmqClusterView) -> ActionPred { - |s: RMQCluster, s_prime: RMQCluster| { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - s.resource_key_exists(sts_key) - && s_prime.resource_key_exists(sts_key) - ==> replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) >= replicas_of_stateful_set(s.resource_obj_of(sts_key)) - } -} - -proof fn lemma_stateful_set_never_scaled_down_for_all(spec: TempPred) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - forall |rabbitmq: RabbitmqClusterView| - spec.entails(always(lift_action(#[trigger] stateful_set_not_scaled_down(rabbitmq)))), -{ - assert forall |rabbitmq| spec.entails(always(lift_action(#[trigger] stateful_set_not_scaled_down(rabbitmq)))) by { - lemma_stateful_set_never_scaled_down_for_rabbitmq(spec, rabbitmq); - } -} - -proof fn lemma_stateful_set_never_scaled_down_for_rabbitmq(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_action(stateful_set_not_scaled_down(rabbitmq)))), -{ - let inv = stateful_set_not_scaled_down(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s) - &&& stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - }; - lemma_always_replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(spec, rabbitmq); - lemma_always_stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(spec, rabbitmq); - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - assert forall |s, s_prime| #[trigger] next(s, s_prime) implies stateful_set_not_scaled_down(rabbitmq)(s, s_prime) by { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - if s.resource_key_exists(sts_key) && s_prime.resource_key_exists(sts_key) { - if s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - let input = step.get_KubernetesAPIStep_0().get_Some_0(); - if input.content.is_delete_request() { - assert(StatefulSetView::from_dynamic_object(s.resource_obj_of(sts_key)).get_Ok_0().spec == StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec); - } else { - assert(input.content.is_update_request()); - assert(s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == input.content.get_update_request().obj.metadata.resource_version.get_Some_0()); - assert(replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) == replicas_of_stateful_set(input.content.get_update_request().obj)); - assert(replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) >= replicas_of_stateful_set(s.resource_obj_of(sts_key))); - } - } - } - } - invariant_n!( - spec, lift_action(next), lift_action(inv), - lift_action(RMQCluster::next()), lift_state(replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)), - lift_state(stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())) - ); -} - -spec fn replicas_of_rabbitmq(obj: DynamicObjectView) -> int - recommends - obj.kind.is_CustomResourceKind(), -{ - RabbitmqClusterView::from_dynamic_object(obj).get_Ok_0().spec.replicas -} - -spec fn replicas_of_stateful_set(obj: DynamicObjectView) -> int - recommends - obj.kind.is_StatefulSetKind(), -{ - StatefulSetView::from_dynamic_object(obj).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() -} - -// Comparing replicas number -// resource_obj_of(sts_key) <= create/update_sts <= triggering_cr_of(cr_key) <= scheduled_cr_of(cr_key) <= resource_obj_of(cr_key) - -spec fn scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - s.reconcile_scheduled_for(rabbitmq.object_ref()) - && s.resource_key_exists(rabbitmq.object_ref()) - && s.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() == s.reconcile_scheduled_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() - ==> s.reconcile_scheduled_obj_of(rabbitmq.object_ref()).spec.replicas <= replicas_of_rabbitmq(s.resource_obj_of(rabbitmq.object_ref())) - } -} - -proof fn lemma_always_scheduled_cr_has_no_larger_replicas_than_current_cr(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)))), -{ - let inv = scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::scheduled_cr_has_lower_uid_than_uid_counter()(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - RMQCluster::lemma_always_scheduled_cr_has_lower_uid_than_uid_counter(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - lift_state(RMQCluster::scheduled_cr_has_lower_uid_than_uid_counter()) - ); - let key = rabbitmq.object_ref(); - assert forall |s, s_prime: RMQCluster| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - if s_prime.reconcile_scheduled_for(key) && s_prime.resource_key_exists(key) - && s_prime.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() == s_prime.reconcile_scheduled_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() { - let step = choose |step: RMQStep| RMQCluster::next_step(s, s_prime, step); - match step { - Step::KubernetesAPIStep(input) => { - assert(s.reconcile_scheduled_for(key) && s.reconcile_scheduled_obj_of(key) == s_prime.reconcile_scheduled_obj_of(key)); - if !s.resource_key_exists(key) { - assert(s_prime.resource_obj_of(key).metadata.uid == Some(s.kubernetes_api_state.uid_counter)); - assert(s_prime.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() != s_prime.reconcile_scheduled_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0()); - } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { - assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).is_Ok()); - assert(RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).is_Ok()); - assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); - } - }, - Step::ScheduleControllerReconcileStep(input) => { - assert(s.resource_key_exists(key) && s.resource_obj_of(key) == s_prime.resource_obj_of(key)); - if !s.reconcile_scheduled_for(key) || s.reconcile_scheduled_obj_of(key) != s_prime.reconcile_scheduled_obj_of(key) { - assert(s_prime.reconcile_scheduled_obj_of(rabbitmq.object_ref()).spec.replicas == replicas_of_rabbitmq(s_prime.resource_obj_of(rabbitmq.object_ref()))); - } - }, - _ => {} - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -spec fn triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - s.reconcile_state_contains(rabbitmq.object_ref()) - && s.resource_key_exists(rabbitmq.object_ref()) - && s.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() == s.triggering_cr_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() - ==> s.triggering_cr_of(rabbitmq.object_ref()).spec.replicas <= replicas_of_rabbitmq(s.resource_obj_of(rabbitmq.object_ref())) - } -} - -spec fn triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - s.reconcile_state_contains(rabbitmq.object_ref()) - && s.reconcile_scheduled_for(rabbitmq.object_ref()) - && s.triggering_cr_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() == s.reconcile_scheduled_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() - ==> s.triggering_cr_of(rabbitmq.object_ref()).spec.replicas <= s.reconcile_scheduled_obj_of(rabbitmq.object_ref()).spec.replicas - } -} - -proof fn lemma_always_triggering_cr_has_no_larger_replicas_than_scheduled_cr_and_current_cr(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)))), - spec.entails(always(lift_state(triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)))), -{ - let inv = |s: RMQCluster| { - &&& triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - &&& triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s) - }; - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()(s) - &&& scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - RMQCluster::lemma_always_triggering_cr_has_lower_uid_than_uid_counter(spec); - lemma_always_scheduled_cr_has_no_larger_replicas_than_current_cr(spec, rabbitmq); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - lift_state(RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()), - lift_state(scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - s_leq_both_to_s_prime_leq_scheduled_cr(rabbitmq, s, s_prime); - s_leq_both_to_s_prime_leq_current_cr(rabbitmq, s, s_prime); - } - init_invariant(spec, RMQCluster::init(), next, inv); - always_weaken(spec, inv, triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)); - always_weaken(spec, inv, triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)); -} - -proof fn s_leq_both_to_s_prime_leq_scheduled_cr(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) - requires - RMQCluster::next()(s, s_prime), - triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s), - triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s), - RMQCluster::each_object_in_etcd_is_well_formed()(s), - ensures - triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s_prime), -{} - -proof fn s_leq_both_to_s_prime_leq_current_cr(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) - requires - RMQCluster::next()(s, s_prime), - scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s), - triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s), - triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s), - RMQCluster::each_object_in_etcd_is_well_formed()(s), - RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()(s), - ensures - triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s_prime), -{ - let key = rabbitmq.object_ref(); - if s_prime.reconcile_state_contains(rabbitmq.object_ref()) && s_prime.resource_key_exists(rabbitmq.object_ref()) - && s_prime.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() == s_prime.triggering_cr_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - if step.is_KubernetesAPIStep() { - assert(s.reconcile_state_contains(key) && s.triggering_cr_of(key) == s_prime.triggering_cr_of(key)); - if !s.resource_key_exists(key) { - assert(s_prime.resource_obj_of(key).metadata.uid == Some(s.kubernetes_api_state.uid_counter)); - assert(s_prime.resource_obj_of(rabbitmq.object_ref()).metadata.uid.get_Some_0() != s_prime.triggering_cr_of(rabbitmq.object_ref()).metadata.uid.get_Some_0()); - } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { - assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).is_Ok()); - assert(RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).is_Ok()); - assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); - } - } else if step.is_ControllerStep() { - assert(s.resource_key_exists(key) && s.resource_obj_of(key) == s_prime.resource_obj_of(key)); - if !s.reconcile_state_contains(key) || s.triggering_cr_of(key) != s_prime.triggering_cr_of(key) { - assert(s_prime.triggering_cr_of(rabbitmq.object_ref()).spec.replicas == s.reconcile_scheduled_obj_of(rabbitmq.object_ref()).spec.replicas); - } - } - } -} - -spec fn response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq: RabbitmqClusterView) -> StatePred { - let key = rabbitmq.object_ref(); - |s: RMQCluster| { - at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s) - ==> s.reconcile_state_of(key).pending_req_msg.is_Some() - && is_get_stateful_set_request(s.pending_req_of(key).content.get_APIRequest_0(), rabbitmq) - && ( - forall |msg: RMQMessage| - #[trigger] s.message_in_flight(msg) - && Message::resp_msg_matches_req_msg(msg, s.pending_req_of(key)) - ==> sts_get_response_msg(key)(msg) - ) - } -} - -proof fn lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)))), -{ - let inv = response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq); - let key = rabbitmq.object_ref(); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::every_in_flight_msg_has_lower_id_than_allocator()(s) - &&& RMQCluster::every_in_flight_or_pending_req_msg_has_unique_id()(s) - &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - RMQCluster::lemma_always_every_in_flight_msg_has_lower_id_than_allocator(spec); - RMQCluster::lemma_always_every_in_flight_or_pending_req_msg_has_unique_id(spec); - RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - lift_state(RMQCluster::every_in_flight_msg_has_lower_id_than_allocator()), - lift_state(RMQCluster::every_in_flight_or_pending_req_msg_has_unique_id()), - lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - if at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s_prime) { - if at_rabbitmq_step(key, RabbitmqReconcileStep::AfterGetStatefulSet)(s) { - assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); - assert(s_prime.reconcile_state_of(key).pending_req_msg.is_Some()); - assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); - } else { - assert(at_rabbitmq_step(key, RabbitmqReconcileStep::AfterCreateRoleBinding)(s)); - assert(s_prime.reconcile_state_of(key).pending_req_msg.is_Some()); - assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); - } - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && Message::resp_msg_matches_req_msg(msg, s_prime.pending_req_of(key)) - implies sts_get_response_msg(key)(msg) by { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - match step { - Step::ControllerStep(input) => { - assert(s.message_in_flight(msg)); - let cr_key = input.1.get_Some_0(); - if cr_key == key { - assert(s.message_in_flight(msg)); - assert(false); - } else { - assert(s.pending_req_of(key) == s_prime.pending_req_of(key)); - assert(sts_get_response_msg(key)(msg)); - } - }, - Step::KubernetesAPIStep(input) => { - assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); - if !s.message_in_flight(msg) { - assert(RMQCluster::in_flight_or_pending_req_message(s, s.pending_req_of(key))); - assert(RMQCluster::in_flight_or_pending_req_message(s, input.get_Some_0())); - assert(is_get_stateful_set_request(s_prime.pending_req_of(key).content.get_APIRequest_0(), rabbitmq)); - assert(msg.content.is_get_response()); - assert(msg == RMQCluster::handle_get_request(s.pending_req_of(key), s.kubernetes_api_state).1); - assert(msg.src.is_KubernetesAPI() - && msg.content.is_get_response()); - if msg.content.get_get_response().res.is_Ok() { - assert(s.resource_key_exists(make_stateful_set_key(key))); - assert(s.resource_obj_of(make_stateful_set_key(key)).object_ref() == make_stateful_set_key(key)); - } - assert(sts_get_response_msg(key)(msg)); - } - }, - Step::KubernetesBusy(input) => { - assert(s.reconcile_state_of(key) == s_prime.reconcile_state_of(key)); - if !s.message_in_flight(msg) { - assert(RMQCluster::in_flight_or_pending_req_message(s, s.pending_req_of(key))); - assert(RMQCluster::in_flight_or_pending_req_message(s, input.get_Some_0())); - assert(msg.src.is_KubernetesAPI()); - assert(msg.content.is_get_response()); - assert(msg.content.get_get_response().res.is_Err()); - } - assert(sts_get_response_msg(key)(msg)); - }, - Step::ClientStep() => { - assert(s.message_in_flight(msg)); - assert(sts_get_response_msg(key)(msg)); - }, - _ => { - assert(sts_get_response_msg(key)(msg)); - } - } - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -spec fn stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - let etcd_rv = s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0(); - forall |msg: RMQMessage| - #[trigger] s.message_in_flight(msg) - ==> ( - sts_update_request_msg(rabbitmq.object_ref())(msg) - ==> msg.content.get_update_request().obj.metadata.resource_version.is_Some() - && msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter - ) && ( - ok_sts_get_response_msg(rabbitmq.object_ref())(msg) - ==> msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.is_Some() - && msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter - ) - } -} - -proof fn lemma_always_stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd( - spec: TempPred, rabbitmq: RabbitmqClusterView -) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)))), -{ - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - let upd_rv_leq = |msg: RMQMessage, s: RMQCluster| { - sts_update_request_msg(rabbitmq.object_ref())(msg) - ==> msg.content.get_update_request().obj.metadata.resource_version.is_Some() - && msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter - }; - let get_rv_leq = |msg: RMQMessage, s: RMQCluster| { - ok_sts_get_response_msg(rabbitmq.object_ref())(msg) - ==> msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.is_Some() - && msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter - }; - let inv = stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - &&& response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s) - &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec, rabbitmq); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), - lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)), - lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - let etcd_rv = s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0(); - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) implies upd_rv_leq(msg, s_prime) && get_rv_leq(msg, s_prime) by { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - if s.message_in_flight(msg) { - assert(s.kubernetes_api_state.resource_version_counter <= s_prime.kubernetes_api_state.resource_version_counter); - } else if sts_update_request_msg(rabbitmq.object_ref())(msg) { - lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); - assert(at_rabbitmq_step(key, RabbitmqReconcileStep::AfterUpdateStatefulSet)(s_prime)); - } else if ok_sts_get_response_msg(rabbitmq.object_ref())(msg) { - let input = step.get_KubernetesAPIStep_0().get_Some_0(); - assert(s.resource_key_exists(input.content.get_get_request().key)); - assert(input.content.get_get_request().key == sts_key); - assert(msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() == s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0()); - assert(s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter); - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -// We have to show current sts obj has no larger replicas than triggering cr, because that is how the update messgae is created. -// The other to <= are used to make the triggering_cr invariant hold (easier to prove) -spec fn replicas_satisfies_order(obj: DynamicObjectView, rabbitmq: RabbitmqClusterView) -> StatePred - recommends - obj.kind.is_StatefulSetKind(), -{ - |s: RMQCluster| { - let key = rabbitmq.object_ref(); - let sts_replicas = replicas_of_stateful_set(obj); - &&& s.resource_key_exists(key) - && obj.metadata.owner_references_only_contains(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0().controller_owner_ref()) - ==> sts_replicas <= replicas_of_rabbitmq(s.resource_obj_of(key)) - &&& s.reconcile_scheduled_for(key) - && obj.metadata.owner_references_only_contains(s.reconcile_scheduled_obj_of(key).controller_owner_ref()) - ==> sts_replicas <= s.reconcile_scheduled_obj_of(key).spec.replicas - &&& s.reconcile_state_contains(key) - && obj.metadata.owner_references_only_contains(s.triggering_cr_of(key).controller_owner_ref()) - ==> sts_replicas <= s.triggering_cr_of(key).spec.replicas - } -} - -spec fn replicas_of_etcd_stateful_set_satisfies_order(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - s.resource_key_exists(sts_key) ==> replicas_satisfies_order(s.resource_obj_of(sts_key), rabbitmq)(s) - } -} - -// if the resource versions do not equal when created, it will never equal each other -spec fn replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - forall |msg: RMQMessage| - #[trigger] s.message_in_flight(msg) - && sts_update_request_msg(rabbitmq.object_ref())(msg) - && s.resource_key_exists(sts_key) - && s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() - ==> replicas_of_stateful_set(s.resource_obj_of(sts_key)) <= replicas_of_stateful_set(msg.content.get_update_request().obj) - } -} - -spec fn helper_3_spec_ok_get_resp(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - forall |msg| - #[trigger] s.message_in_flight(msg) - && ok_sts_get_response_msg(key)(msg) - && s.resource_key_exists(sts_key) - && s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() - ==> s.resource_obj_of(sts_key) == msg.content.get_get_response().res.get_Ok_0() - } -} - -proof fn lemma_always_helper_3_spec(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(helper_3_spec_ok_get_resp(rabbitmq)))), -{ - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - let inv = helper_3_spec_ok_get_resp(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - lemma_always_stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(spec, rabbitmq); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - lift_state(stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && ok_sts_get_response_msg(key)(msg) && s_prime.resource_key_exists(sts_key) - && s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() implies s_prime.resource_obj_of(sts_key) == msg.content.get_get_response().res.get_Ok_0() by { - if s.message_in_flight(msg) { - if !s.resource_key_exists(sts_key) || s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { - assert(s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() != msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0()) - } - } else { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - assert(step.is_KubernetesAPIStep()); - let req = step.get_KubernetesAPIStep_0().get_Some_0(); - assert(msg == RMQCluster::handle_get_request(req, s.kubernetes_api_state).1); - assert(s.resource_key_exists(req.content.get_get_request().key)); - assert(msg.content.get_get_response().res.get_Ok_0() == s.resource_obj_of(req.content.get_get_request().key)); - assert(req.content.get_get_request().key == msg.content.get_get_response().res.get_Ok_0().object_ref()); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - assert(s_prime.resource_obj_of(sts_key) == msg.content.get_get_response().res.get_Ok_0()); - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -proof fn lemma_always_replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)))), -{ - let inv = replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - &&& replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime) - &&& stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)(s) - &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - &&& helper_3_spec_ok_get_resp(rabbitmq)(s) - &&& response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - lemma_always_replicas_of_etcd_stateful_set_satisfies_order(spec, rabbitmq); - always_to_always_later(spec, lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq))); - lemma_always_stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(spec, rabbitmq); - RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - lemma_always_helper_3_spec(spec, rabbitmq); - lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec, rabbitmq); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), - later(lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq))), - lift_state(stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)), - lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), - lift_state(helper_3_spec_ok_get_resp(rabbitmq)), - lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - helper_2(rabbitmq, s, s_prime); - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - - -proof fn helper_2(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) - requires - RMQCluster::next()(s, s_prime), - RMQCluster::each_object_in_etcd_is_well_formed()(s), - RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), - replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime), - replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s), - stateful_set_in_get_response_and_update_request_have_no_larger_resource_version_than_etcd(rabbitmq)(s), - RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s), - helper_3_spec_ok_get_resp(rabbitmq)(s), - response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s), - ensures - replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s_prime), -{ - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && sts_update_request_msg(rabbitmq.object_ref())(msg) - && s_prime.resource_key_exists(sts_key) - && s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() implies StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() - <= replicas_of_stateful_set(msg.content.get_update_request().obj) by { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - if s.message_in_flight(msg) { - if !s.resource_key_exists(sts_key) || s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { - assert(s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == s.kubernetes_api_state.resource_version_counter); - assert(msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter); - assert(false); - } else { - assert(StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= replicas_of_stateful_set(msg.content.get_update_request().obj)); - } - } else { - lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); - StatefulSetView::spec_integrity_is_preserved_by_marshal(); - assert(s_prime.resource_obj_of(sts_key) == s.resource_obj_of(sts_key)); - assert(replicas_of_stateful_set(msg.content.get_update_request().obj) == s.triggering_cr_of(key).spec.replicas); - assert(s.triggering_cr_of(key) == s_prime.triggering_cr_of(key)); - assert(s_prime.resource_obj_of(sts_key).metadata.owner_references_only_contains(s.triggering_cr_of(key).controller_owner_ref())); - assert(StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= replicas_of_stateful_set(msg.content.get_update_request().obj)); - } - } -} - -proof fn lemma_always_replicas_of_etcd_stateful_set_satisfies_order(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)))), -{ - let inv = replicas_of_etcd_stateful_set_satisfies_order(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - &&& every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()(s) - &&& replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq)(s) - &&& replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq)(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - lemma_always_every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(spec); - lemma_always_replicas_of_stateful_set_create_request_msg_satisfies_order(spec, rabbitmq); - lemma_always_replicas_of_stateful_set_update_request_msg_satisfies_order(spec, rabbitmq); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), - lift_state(every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()), - lift_state(replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq)), - lift_state(replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq)) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - helper_1(rabbitmq, s, s_prime); - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -proof fn helper_1(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) - requires - RMQCluster::next()(s, s_prime), - replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s), - replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq)(s), - replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq)(s), - RMQCluster::each_object_in_etcd_is_well_formed()(s), - RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), - every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()(s), - ensures - replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime), -{ - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - if s_prime.resource_key_exists(sts_key) { - if s.resource_key_exists(sts_key) && s.resource_obj_of(sts_key) == s_prime.resource_obj_of(sts_key) { - if s_prime.resource_key_exists(key) { - if !s.resource_key_exists(key) { - assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); - let owner_refs = s.resource_obj_of(sts_key).metadata.owner_references; - if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { - assert(owner_refs.get_Some_0()[0].uid != s.kubernetes_api_state.uid_counter); - assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); - } - } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { - assert(s.resource_obj_of(key).metadata.uid == s_prime.resource_obj_of(key).metadata.uid); - assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).is_Ok()); - assert(RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).is_Ok()); - assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0().controller_owner_ref() == RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); - assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); - } - } - if s_prime.reconcile_scheduled_for(key) { - if !s.reconcile_scheduled_for(key) || s.reconcile_scheduled_obj_of(key) != s_prime.reconcile_scheduled_obj_of(key) { - assert(s_prime.reconcile_scheduled_obj_of(key) == RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0()); - } - } - if s_prime.reconcile_state_contains(key) { - if !s.reconcile_state_contains(key) || s.triggering_cr_of(key) != s_prime.triggering_cr_of(key) { - assert(s_prime.triggering_cr_of(key) == s.reconcile_scheduled_obj_of(key)); - } - } - } else if s.resource_key_exists(sts_key) && s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { - assert(replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq)(s)); - assert(replicas_satisfies_order(s_prime.resource_obj_of(sts_key), rabbitmq)(s_prime)); - } else { - assert(replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq)(s)); - assert(replicas_satisfies_order(s_prime.resource_obj_of(sts_key), rabbitmq)(s_prime)); - } - } -} - -spec fn owner_references_is_valid(obj: DynamicObjectView, s: RMQCluster) -> bool { - if obj.kind.is_CustomResourceKind() { - true - } else { - let owner_refs = obj.metadata.owner_references.get_Some_0(); - forall |i| - #![auto] 0 <= i < owner_refs.len() - ==> owner_refs[i].uid < s.kubernetes_api_state.uid_counter - } - -} - -spec fn object_in_every_create_or_update_request_msg_only_has_valid_owner_references() -> StatePred { - |s: RMQCluster| { - forall |msg| - #[trigger] s.message_in_flight(msg) - && msg.dst.is_KubernetesAPI() - && msg.content.is_APIRequest() - ==> ( - msg.content.is_create_request() && msg.content.get_create_request().obj.metadata.owner_references.is_Some() - ==> owner_references_is_valid(msg.content.get_create_request().obj, s) - ) && ( - msg.content.is_update_request() && msg.content.get_update_request().obj.metadata.owner_references.is_Some() - ==> owner_references_is_valid(msg.content.get_update_request().obj, s) - ) - } -} - -proof fn lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec: TempPred) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references()))), -{ - let inv = object_in_every_create_or_update_request_msg_only_has_valid_owner_references(); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()(s) - }; - RMQCluster::lemma_always_triggering_cr_has_lower_uid_than_uid_counter(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::triggering_cr_has_lower_uid_than_uid_counter()) - ); - let create_valid = |msg: RMQMessage, s: RMQCluster| { - msg.content.is_create_request() && msg.content.get_create_request().obj.metadata.owner_references.is_Some() - ==> owner_references_is_valid(msg.content.get_create_request().obj, s) - }; - let update_valid = |msg: RMQMessage, s: RMQCluster| { - msg.content.is_update_request() && msg.content.get_update_request().obj.metadata.owner_references.is_Some() - ==> owner_references_is_valid(msg.content.get_update_request().obj, s) - }; - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && msg.dst.is_KubernetesAPI() && msg.content.is_APIRequest() - implies create_valid(msg, s_prime) && update_valid(msg, s_prime) by { - assert(s.kubernetes_api_state.uid_counter <= s_prime.kubernetes_api_state.uid_counter); - if !s.message_in_flight(msg) { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - match step { - Step::ControllerStep(input) => { - let cr = s.triggering_cr_of(input.1.get_Some_0()); - if msg.content.is_create_request() { - let owner_refs = msg.content.get_create_request().obj.metadata.owner_references; - if owner_refs.is_Some() { - assert(owner_refs == Some(seq![cr.controller_owner_ref()])); - } - } else if msg.content.is_update_request() { - let owner_refs = msg.content.get_update_request().obj.metadata.owner_references; - if owner_refs.is_Some() { - assert(owner_refs == Some(seq![cr.controller_owner_ref()])); - } - } - }, - Step::ClientStep() => { - if msg.content.is_create_request() { - assert(msg.content.get_create_request().obj.kind.is_CustomResourceKind()); - } else if msg.content.is_update_request() { - assert(msg.content.get_update_request().obj.kind.is_CustomResourceKind()); - } - }, - Step::BuiltinControllersStep(_) => { - assert(!msg.content.is_update_request() && !msg.content.is_create_request()); - }, - _ => {} - } - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -spec fn every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter() -> StatePred { - |s: RMQCluster| { - forall |key| - #[trigger] s.resource_key_exists(key) - && s.resource_obj_of(key).metadata.owner_references.is_Some() - ==> owner_references_is_valid(s.resource_obj_of(key), s) - } -} - -proof fn lemma_always_every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(spec: TempPred) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()))), -{ - let inv = every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s) - }; - lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec); - combine_spec_entails_always_n!(spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references())); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - assert forall |key| - #[trigger] s_prime.resource_key_exists(key) && s_prime.resource_obj_of(key).metadata.owner_references.is_Some() - implies owner_references_is_valid(s_prime.resource_obj_of(key), s_prime) by { - assert(s.kubernetes_api_state.uid_counter <= s_prime.kubernetes_api_state.uid_counter); - if !s.resource_key_exists(key) || s.resource_obj_of(key).metadata.owner_references != s_prime.resource_obj_of(key).metadata.owner_references {} else {} - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -// show that update msg resource version <= s.resource_obj_of(sts_key) resource version -spec fn replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - forall |msg: RMQMessage| - #[trigger] s.message_in_flight(msg) - && sts_update_request_msg(rabbitmq.object_ref())(msg) - ==> msg.content.get_update_request().obj.kind == Kind::StatefulSetKind - && replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s) - } -} - -// show that create msg resource version <= s.resource_obj_of(sts_key) resource version -spec fn replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq: RabbitmqClusterView) -> StatePred { - |s: RMQCluster| { - let sts_key = make_stateful_set_key(rabbitmq.object_ref()); - forall |msg: RMQMessage| - #[trigger] s.message_in_flight(msg) - && sts_create_request_msg(rabbitmq.object_ref())(msg) - ==> replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s) - } -} - -// create and update msg should be similar except that update requires another invariant -proof fn lemma_always_replicas_of_stateful_set_create_request_msg_satisfies_order(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq)))), -{ - let inv = replicas_of_stateful_set_create_request_msg_satisfies_order(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - &&& scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - &&& triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - &&& triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s) - &&& object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - lemma_always_scheduled_cr_has_no_larger_replicas_than_current_cr(spec, rabbitmq); - lemma_always_triggering_cr_has_no_larger_replicas_than_scheduled_cr_and_current_cr(spec, rabbitmq); - lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), - lift_action(RMQCluster::next()), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), - lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), - lift_state(scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)), - lift_state(triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)), - lift_state(triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)), - lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references()) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && sts_create_request_msg(rabbitmq.object_ref())(msg) - implies replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime) by { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - match step { - Step::KubernetesAPIStep(input) => { - assert(s.message_in_flight(msg)); - assert(s.controller_state == s_prime.controller_state); - if s_prime.resource_key_exists(key) { - if s.resource_key_exists(key) { - assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); - } else { - assert(s_prime.resource_obj_of(key).metadata.uid.is_Some()); - assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); - let owner_refs = msg.content.get_create_request().obj.metadata.owner_references; - if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { - assert(owner_refs.get_Some_0()[0].uid != s.kubernetes_api_state.uid_counter); - assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); - } - } - } - assert(replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)); - }, - Step::ControllerStep(input) => { - if !s.message_in_flight(msg) { - lemma_stateful_set_create_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); - let cr = s.triggering_cr_of(key); - StatefulSetView::spec_integrity_is_preserved_by_marshal(); - assert(replicas_of_stateful_set(msg.content.get_create_request().obj) == cr.spec.replicas); - } else { - assert(replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)); - } - }, - Step::ScheduleControllerReconcileStep(input) => { - assert(s.message_in_flight(msg)); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - if input == key {} else {} - assert(replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)); - }, - Step::RestartController() => { - assert(s.message_in_flight(msg)); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - assert(!s_prime.reconcile_state_contains(key)); - assert(!s_prime.reconcile_scheduled_for(key)); - assert(replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)); - }, - _ => { - assert(s.message_in_flight(msg)); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - assert(s.controller_state == s_prime.controller_state); - assert(replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)); - } - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -proof fn lemma_always_replicas_of_stateful_set_update_request_msg_satisfies_order(spec: TempPred, rabbitmq: RabbitmqClusterView) - requires - spec.entails(lift_state(RMQCluster::init())), - spec.entails(always(lift_action(RMQCluster::next()))), - ensures - spec.entails(always(lift_state(replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq)))), -{ - let inv = replicas_of_stateful_set_update_request_msg_satisfies_order(rabbitmq); - let next = |s, s_prime| { - &&& RMQCluster::next()(s, s_prime) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) - &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) - &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) - &&& scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - &&& triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)(s) - &&& triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)(s) - &&& object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s) - }; - RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); - always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); - RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); - lemma_always_scheduled_cr_has_no_larger_replicas_than_current_cr(spec, rabbitmq); - lemma_always_triggering_cr_has_no_larger_replicas_than_scheduled_cr_and_current_cr(spec, rabbitmq); - lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec); - combine_spec_entails_always_n!( - spec, lift_action(next), - lift_action(RMQCluster::next()), - lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), - later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), - lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), - lift_state(scheduled_cr_has_no_larger_replicas_than_current_cr(rabbitmq)), - lift_state(triggering_cr_has_no_larger_replicas_than_current_cr(rabbitmq)), - lift_state(triggering_cr_has_no_larger_replicas_than_scheduled_cr(rabbitmq)), - lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references()) - ); - assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { - assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && sts_update_request_msg(rabbitmq.object_ref())(msg) - implies msg.content.get_update_request().obj.kind == Kind::StatefulSetKind && replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime) by { - let step = choose |step| RMQCluster::next_step(s, s_prime, step); - let key = rabbitmq.object_ref(); - let sts_key = make_stateful_set_key(key); - match step { - Step::KubernetesAPIStep(input) => { - assert(s.message_in_flight(msg)); - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - assert(s.controller_state == s_prime.controller_state); - if s_prime.resource_key_exists(key) { - if s.resource_key_exists(key) { - assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); - } else { - assert(s_prime.resource_obj_of(key).metadata.uid.is_Some()); - assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); - let owner_refs = msg.content.get_update_request().obj.metadata.owner_references; - assert(s.message_in_flight(msg) - && msg.dst.is_KubernetesAPI() - && msg.content.is_APIRequest()); - if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { - assert(owner_references_is_valid(msg.content.get_update_request().obj, s)); - assert(!msg.content.get_update_request().obj.kind.is_CustomResourceKind()); - assert(owner_refs.get_Some_0()[0].uid < s.kubernetes_api_state.uid_counter); - assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); - } - } - } - assert(replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)); - }, - Step::ControllerStep(input) => { - if !s.message_in_flight(msg) { - lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); - let cr = s.triggering_cr_of(key); - StatefulSetView::spec_integrity_is_preserved_by_marshal(); - assert(replicas_of_stateful_set(msg.content.get_update_request().obj) == cr.spec.replicas); - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - } else { - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - assert(replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)); - } - }, - Step::ScheduleControllerReconcileStep(input) => { - assert(s.message_in_flight(msg)); - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - if input == key {} else {} - assert(replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)); - }, - Step::RestartController() => { - assert(s.message_in_flight(msg)); - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - assert(!s_prime.reconcile_state_contains(key)); - assert(!s_prime.reconcile_scheduled_for(key)); - assert(replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)); - }, - _ => { - assert(s.message_in_flight(msg)); - assert(msg.content.get_update_request().obj.kind == Kind::StatefulSetKind); - assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); - assert(s.controller_state == s_prime.controller_state); - assert(replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)); - } - } - } - } - init_invariant(spec, RMQCluster::init(), next, inv); -} - -} diff --git a/src/controller_examples/rabbitmq_controller/proof/safety/mod.rs b/src/controller_examples/rabbitmq_controller/proof/safety/mod.rs new file mode 100644 index 000000000..b29d3c560 --- /dev/null +++ b/src/controller_examples/rabbitmq_controller/proof/safety/mod.rs @@ -0,0 +1,3 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +pub mod safety; \ No newline at end of file diff --git a/src/controller_examples/rabbitmq_controller/proof/safety/safety.rs b/src/controller_examples/rabbitmq_controller/proof/safety/safety.rs new file mode 100644 index 000000000..d1fde9e1f --- /dev/null +++ b/src/controller_examples/rabbitmq_controller/proof/safety/safety.rs @@ -0,0 +1,557 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::{ + api_method::*, common::*, dynamic::*, resource::*, stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::common::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::rabbitmq_controller::{ + common::*, + proof::{common::*, helper_invariants::*}, + spec::{rabbitmqcluster::*, reconciler::*}, +}; +use crate::temporal_logic::{defs::*, rules::*}; +use vstd::prelude::*; + +verus! { + +spec fn stateful_set_not_scaled_down(rabbitmq: RabbitmqClusterView) -> ActionPred { + |s: RMQCluster, s_prime: RMQCluster| { + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + s.resource_key_exists(sts_key) + && s_prime.resource_key_exists(sts_key) + ==> replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) >= replicas_of_stateful_set(s.resource_obj_of(sts_key)) + } +} + +proof fn lemma_stateful_set_never_scaled_down_for_all(spec: TempPred) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + forall |rabbitmq: RabbitmqClusterView| + spec.entails(always(lift_action(#[trigger] stateful_set_not_scaled_down(rabbitmq)))), +{ + assert forall |rabbitmq| spec.entails(always(lift_action(#[trigger] stateful_set_not_scaled_down(rabbitmq)))) by { + lemma_stateful_set_never_scaled_down_for_rabbitmq(spec, rabbitmq); + } +} + +proof fn lemma_stateful_set_never_scaled_down_for_rabbitmq(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_action(stateful_set_not_scaled_down(rabbitmq)))), +{ + let inv = stateful_set_not_scaled_down(rabbitmq); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s) + &&& object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) + }; + lemma_always_replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(spec, rabbitmq); + lemma_always_object_in_sts_update_request_has_smaller_rv_than_etcd(spec, rabbitmq); + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); + assert forall |s, s_prime| #[trigger] next(s, s_prime) implies stateful_set_not_scaled_down(rabbitmq)(s, s_prime) by { + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + if s.resource_key_exists(sts_key) && s_prime.resource_key_exists(sts_key) { + if s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + let input = step.get_KubernetesAPIStep_0().get_Some_0(); + if input.content.is_delete_request() { + assert(StatefulSetView::from_dynamic_object(s.resource_obj_of(sts_key)).get_Ok_0().spec == StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec); + } else { + assert(input.content.is_update_request()); + assert(s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == input.content.get_update_request().obj.metadata.resource_version.get_Some_0()); + assert(replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) == replicas_of_stateful_set(input.content.get_update_request().obj)); + assert(replicas_of_stateful_set(s_prime.resource_obj_of(sts_key)) >= replicas_of_stateful_set(s.resource_obj_of(sts_key))); + } + } + } + } + invariant_n!( + spec, lift_action(next), lift_action(inv), + lift_action(RMQCluster::next()), lift_state(replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)), + lift_state(object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)), + lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())) + ); +} + +spec fn replicas_of_rabbitmq(obj: DynamicObjectView) -> int + recommends + obj.kind.is_CustomResourceKind(), +{ + RabbitmqClusterView::from_dynamic_object(obj).get_Ok_0().spec.replicas +} + +spec fn replicas_of_stateful_set(obj: DynamicObjectView) -> int + recommends + obj.kind.is_StatefulSetKind(), +{ + StatefulSetView::from_dynamic_object(obj).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() +} + +spec fn object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq: RabbitmqClusterView) -> StatePred { + |s: RMQCluster| { + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + let etcd_rv = s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0(); + forall |msg: RMQMessage| + #[trigger] s.message_in_flight(msg) + && sts_update_request_msg(rabbitmq.object_ref())(msg) + ==> msg.content.get_update_request().obj.metadata.resource_version.is_Some() + && msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter + } +} + +proof fn lemma_always_object_in_sts_update_request_has_smaller_rv_than_etcd( + spec: TempPred, rabbitmq: RabbitmqClusterView +) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)))), +{ + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + let upd_rv_leq = |msg: RMQMessage, s: RMQCluster| { + sts_update_request_msg(rabbitmq.object_ref())(msg) + ==> msg.content.get_update_request().obj.metadata.resource_version.is_Some() + && msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter + }; + let inv = object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) + &&& response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s) + &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& RMQCluster::object_in_ok_get_response_has_smaller_rv_than_etcd(sts_key)(s) + }; + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec, rabbitmq); + always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); + RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + RMQCluster::lemma_always_object_in_ok_get_response_has_smaller_rv_than_etcd(spec, sts_key); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(RMQCluster::next()), + lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), + later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), + lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)), + lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(RMQCluster::object_in_ok_get_response_has_smaller_rv_than_etcd(sts_key)) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + let etcd_rv = s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0(); + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && sts_update_request_msg(rabbitmq.object_ref())(msg) implies + msg.content.get_update_request().obj.metadata.resource_version.is_Some() + && msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s_prime.kubernetes_api_state.resource_version_counter by { + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + if s.message_in_flight(msg) { + assert(s.kubernetes_api_state.resource_version_counter <= s_prime.kubernetes_api_state.resource_version_counter); + } else if sts_update_request_msg(rabbitmq.object_ref())(msg) { + lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); + assert(at_rabbitmq_step(key, RabbitmqReconcileStep::AfterUpdateStatefulSet)(s_prime)); + } + } + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + +spec fn replicas_satisfies_order(obj: DynamicObjectView, rabbitmq: RabbitmqClusterView) -> StatePred + recommends + obj.kind.is_StatefulSetKind(), +{ + |s: RMQCluster| { + let key = rabbitmq.object_ref(); + let sts_replicas = replicas_of_stateful_set(obj); + &&& s.resource_key_exists(key) + && obj.metadata.owner_references_only_contains(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0().controller_owner_ref()) + ==> sts_replicas <= replicas_of_rabbitmq(s.resource_obj_of(key)) + &&& s.reconcile_scheduled_for(key) + && obj.metadata.owner_references_only_contains(s.reconcile_scheduled_obj_of(key).controller_owner_ref()) + ==> sts_replicas <= s.reconcile_scheduled_obj_of(key).spec.replicas + &&& s.reconcile_state_contains(key) + && obj.metadata.owner_references_only_contains(s.triggering_cr_of(key).controller_owner_ref()) + ==> sts_replicas <= s.triggering_cr_of(key).spec.replicas + } +} + +spec fn replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq: RabbitmqClusterView) -> StatePred { + |s: RMQCluster| { + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + forall |msg: RMQMessage| + #[trigger] s.message_in_flight(msg) + && sts_update_request_msg(rabbitmq.object_ref())(msg) + && s.resource_key_exists(sts_key) + && s.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() + ==> replicas_of_stateful_set(s.resource_obj_of(sts_key)) <= replicas_of_stateful_set(msg.content.get_update_request().obj) + } +} + +proof fn lemma_always_replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)))), +{ + let inv = replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq); + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) + &&& replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime) + &&& object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)(s) + &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& RMQCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(sts_key)(s) + &&& response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s) + }; + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); + lemma_always_replicas_of_etcd_stateful_set_satisfies_order(spec, rabbitmq); + always_to_always_later(spec, lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq))); + lemma_always_object_in_sts_update_request_has_smaller_rv_than_etcd(spec, rabbitmq); + RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + RMQCluster::lemma_always_object_in_ok_get_resp_is_same_as_etcd_with_same_rv(spec, sts_key); + lemma_always_response_at_after_get_stateful_set_step_is_sts_get_response(spec, rabbitmq); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(RMQCluster::next()), + lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), + later(lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq))), + lift_state(object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)), + lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(RMQCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(sts_key)), + lift_state(response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd_induction(rabbitmq, s, s_prime); + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + + +proof fn replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd_induction(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) + requires + RMQCluster::next()(s, s_prime), + RMQCluster::each_object_in_etcd_is_well_formed()(s), + RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), + replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime), + replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s), + object_in_sts_update_request_has_smaller_rv_than_etcd(rabbitmq)(s), + RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s), + RMQCluster::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(make_stateful_set_key(rabbitmq.object_ref()))(s), + response_at_after_get_stateful_set_step_is_sts_get_response(rabbitmq)(s), + ensures + replicas_of_stateful_set_update_request_msg_is_no_smaller_than_etcd(rabbitmq)(s_prime), +{ + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && sts_update_request_msg(rabbitmq.object_ref())(msg) + && s_prime.resource_key_exists(sts_key) + && s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() implies StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() + <= replicas_of_stateful_set(msg.content.get_update_request().obj) by { + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + if s.message_in_flight(msg) { + if !s.resource_key_exists(sts_key) || s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { + assert(s_prime.resource_obj_of(sts_key).metadata.resource_version.get_Some_0() == s.kubernetes_api_state.resource_version_counter); + assert(msg.content.get_update_request().obj.metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter); + assert(false); + } else { + assert(StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= replicas_of_stateful_set(msg.content.get_update_request().obj)); + } + } else { + lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); + StatefulSetView::spec_integrity_is_preserved_by_marshal(); + assert(s_prime.resource_obj_of(sts_key) == s.resource_obj_of(sts_key)); + assert(replicas_of_stateful_set(msg.content.get_update_request().obj) == s.triggering_cr_of(key).spec.replicas); + assert(s.triggering_cr_of(key) == s_prime.triggering_cr_of(key)); + assert(s_prime.resource_obj_of(sts_key).metadata.owner_references_only_contains(s.triggering_cr_of(key).controller_owner_ref())); + assert(StatefulSetView::from_dynamic_object(s_prime.resource_obj_of(sts_key)).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= replicas_of_stateful_set(msg.content.get_update_request().obj)); + } + } +} + +spec fn replicas_of_etcd_stateful_set_satisfies_order(rabbitmq: RabbitmqClusterView) -> StatePred { + |s: RMQCluster| { + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + s.resource_key_exists(sts_key) ==> replicas_satisfies_order(s.resource_obj_of(sts_key), rabbitmq)(s) + } +} + +proof fn lemma_always_replicas_of_etcd_stateful_set_satisfies_order(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)))), +{ + let inv = replicas_of_etcd_stateful_set_satisfies_order(rabbitmq); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) + &&& every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()(s) + &&& replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s) + }; + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); + lemma_always_every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter(spec); + lemma_always_replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(spec, rabbitmq); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(RMQCluster::next()), lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), + later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), + lift_state(every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()), + lift_state(replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + replicas_of_etcd_stateful_set_satisfies_order_induction(rabbitmq, s, s_prime); + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + +proof fn replicas_of_etcd_stateful_set_satisfies_order_induction(rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster) + requires + RMQCluster::next()(s, s_prime), + replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s), + replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s), + RMQCluster::each_object_in_etcd_is_well_formed()(s), + RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), + every_owner_ref_of_every_object_in_etcd_has_different_uid_from_uid_counter()(s), + ensures + replicas_of_etcd_stateful_set_satisfies_order(rabbitmq)(s_prime), +{ + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + if s_prime.resource_key_exists(sts_key) { + if s.resource_key_exists(sts_key) && s.resource_obj_of(sts_key) == s_prime.resource_obj_of(sts_key) { + if s_prime.resource_key_exists(key) { + if !s.resource_key_exists(key) { + assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); + let owner_refs = s.resource_obj_of(sts_key).metadata.owner_references; + if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { + assert(owner_refs.get_Some_0()[0].uid != s.kubernetes_api_state.uid_counter); + assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); + } + } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { + assert(s.resource_obj_of(key).metadata.uid == s_prime.resource_obj_of(key).metadata.uid); + assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).is_Ok()); + assert(RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).is_Ok()); + assert(RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0().controller_owner_ref() == RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); + assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); + } + } + if s_prime.reconcile_scheduled_for(key) { + if !s.reconcile_scheduled_for(key) || s.reconcile_scheduled_obj_of(key) != s_prime.reconcile_scheduled_obj_of(key) { + assert(s_prime.reconcile_scheduled_obj_of(key) == RabbitmqClusterView::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0()); + } + } + if s_prime.reconcile_state_contains(key) { + if !s.reconcile_state_contains(key) || s.triggering_cr_of(key) != s_prime.triggering_cr_of(key) { + assert(s_prime.triggering_cr_of(key) == s.reconcile_scheduled_obj_of(key)); + } + } + } else if s.resource_key_exists(sts_key) && s.resource_obj_of(sts_key) != s_prime.resource_obj_of(sts_key) { + assert(replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s)); + assert(replicas_satisfies_order(s_prime.resource_obj_of(sts_key), rabbitmq)(s_prime)); + } else { + assert(replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s)); + assert(replicas_satisfies_order(s_prime.resource_obj_of(sts_key), rabbitmq)(s_prime)); + } + } +} + +spec fn replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq: RabbitmqClusterView) -> StatePred { + |s: RMQCluster| { + let sts_key = make_stateful_set_key(rabbitmq.object_ref()); + forall |msg: RMQMessage| + #[trigger] s.message_in_flight(msg) + ==> ( + sts_create_request_msg(rabbitmq.object_ref())(msg) + ==> replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s) + ) && ( + sts_update_request_msg(rabbitmq.object_ref())(msg) + ==> replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s) + ) + } +} + +proof fn lemma_always_replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(spec: TempPred, rabbitmq: RabbitmqClusterView) + requires + spec.entails(lift_state(RMQCluster::init())), + spec.entails(always(lift_action(RMQCluster::next()))), + ensures + spec.entails(always(lift_state(replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)))), +{ + let inv = replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq); + let next = |s, s_prime| { + &&& RMQCluster::next()(s, s_prime) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s) + &&& RMQCluster::each_object_in_etcd_is_well_formed()(s_prime) + &&& RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + &&& RMQCluster::etcd_and_scheduled_and_triggering_cr_in_correct_order(rabbitmq)(s) + &&& object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s) + }; + RMQCluster::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(RMQCluster::each_object_in_etcd_is_well_formed())); + RMQCluster::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + RMQCluster::lemma_always_etcd_and_scheduled_and_triggering_cr_in_correct_order(spec, rabbitmq); + lemma_always_object_in_every_create_or_update_request_msg_only_has_valid_owner_references(spec); + combine_spec_entails_always_n!( + spec, lift_action(next), + lift_action(RMQCluster::next()), + lift_state(RMQCluster::each_object_in_etcd_is_well_formed()), + later(lift_state(RMQCluster::each_object_in_etcd_is_well_formed())), + lift_state(RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()), + lift_state(RMQCluster::etcd_and_scheduled_and_triggering_cr_in_correct_order(rabbitmq)), + lift_state(object_in_every_create_or_update_request_msg_only_has_valid_owner_references()) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) implies (sts_create_request_msg(rabbitmq.object_ref())(msg) + ==> replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime)) && (sts_update_request_msg(rabbitmq.object_ref())(msg) + ==> replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime)) by { + if sts_create_request_msg(rabbitmq.object_ref())(msg) { + replicas_of_stateful_set_create_request_msg_satisfies_order_induction(rabbitmq, s, s_prime, msg); + } + if sts_update_request_msg(rabbitmq.object_ref())(msg) { + replicas_of_stateful_set_update_request_msg_satisfies_order_induction(rabbitmq, s, s_prime, msg); + } + } + } + init_invariant(spec, RMQCluster::init(), next, inv); +} + +proof fn replicas_of_stateful_set_create_request_msg_satisfies_order_induction( + rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster, msg: RMQMessage +) + requires + RMQCluster::next()(s, s_prime), + RMQCluster::each_object_in_etcd_is_well_formed()(s), + RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), + RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s), + RMQCluster::etcd_and_scheduled_and_triggering_cr_in_correct_order(rabbitmq)(s), + object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s), + replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s), + s_prime.message_in_flight(msg), + sts_create_request_msg(rabbitmq.object_ref())(msg), + ensures + replicas_satisfies_order(msg.content.get_create_request().obj, rabbitmq)(s_prime), +{ + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + match step { + Step::KubernetesAPIStep(input) => { + assert(s.controller_state == s_prime.controller_state); + assert(s.message_in_flight(msg)); + if s_prime.resource_key_exists(key) { + if s.resource_key_exists(key) { + assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); + } else { + assert(s_prime.resource_obj_of(key).metadata.uid.is_Some()); + assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); + let owner_refs = msg.content.get_create_request().obj.metadata.owner_references; + if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { + assert(owner_refs.get_Some_0()[0].uid != s.kubernetes_api_state.uid_counter); + assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); + } + } + } + }, + Step::ControllerStep(input) => { + if !s.message_in_flight(msg) { + StatefulSetView::spec_integrity_is_preserved_by_marshal(); + lemma_stateful_set_create_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); + assert(StatefulSetView::from_dynamic_object(msg.content.get_create_request().obj).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= s.triggering_cr_of(key).spec.replicas); + } + }, + Step::ScheduleControllerReconcileStep(input) => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + }, + Step::RestartController() => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + }, + _ => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + assert(s.controller_state == s_prime.controller_state); + } + } +} + +proof fn replicas_of_stateful_set_update_request_msg_satisfies_order_induction( + rabbitmq: RabbitmqClusterView, s: RMQCluster, s_prime: RMQCluster, msg: RMQMessage +) + requires + RMQCluster::next()(s, s_prime), + RMQCluster::each_object_in_etcd_is_well_formed()(s), + RMQCluster::each_object_in_etcd_is_well_formed()(s_prime), + RMQCluster::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s), + RMQCluster::etcd_and_scheduled_and_triggering_cr_in_correct_order(rabbitmq)(s), + object_in_every_create_or_update_request_msg_only_has_valid_owner_references()(s), + replicas_of_stateful_set_create_or_update_request_msg_satisfies_order(rabbitmq)(s), + s_prime.message_in_flight(msg), + sts_update_request_msg(rabbitmq.object_ref())(msg), + ensures + replicas_satisfies_order(msg.content.get_update_request().obj, rabbitmq)(s_prime), +{ + let step = choose |step| RMQCluster::next_step(s, s_prime, step); + let key = rabbitmq.object_ref(); + let sts_key = make_stateful_set_key(key); + match step { + Step::KubernetesAPIStep(input) => { + assert(s.message_in_flight(msg)); + assert(s.controller_state == s_prime.controller_state); + if s_prime.resource_key_exists(key) { + if s.resource_key_exists(key) { + assert(replicas_of_rabbitmq(s.resource_obj_of(key)) <= replicas_of_rabbitmq(s_prime.resource_obj_of(key))); + } else { + assert(s_prime.resource_obj_of(key).metadata.uid.is_Some()); + assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s.kubernetes_api_state.uid_counter); + let owner_refs = msg.content.get_update_request().obj.metadata.owner_references; + if owner_refs.is_Some() && owner_refs.get_Some_0().len() == 1 { + assert(owner_refs.get_Some_0()[0].uid < s.kubernetes_api_state.uid_counter); + assert(owner_refs.get_Some_0()[0] != RabbitmqClusterView::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0().controller_owner_ref()); + } + } + } + }, + Step::ControllerStep(input) => { + if !s.message_in_flight(msg) { + StatefulSetView::spec_integrity_is_preserved_by_marshal(); + lemma_stateful_set_update_request_msg_implies_key_in_reconcile_equals(key, s, s_prime, msg, step); + assert(StatefulSetView::from_dynamic_object(msg.content.get_update_request().obj).get_Ok_0().spec.get_Some_0().replicas.get_Some_0() <= s.triggering_cr_of(key).spec.replicas); + } + }, + Step::ScheduleControllerReconcileStep(input) => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + }, + Step::RestartController() => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + }, + _ => { + assert(s.message_in_flight(msg)); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + assert(s.controller_state == s_prime.controller_state); + } + } +} + +} diff --git a/src/kubernetes_api_objects/resource.rs b/src/kubernetes_api_objects/resource.rs index dbb034a9a..75dff9382 100644 --- a/src/kubernetes_api_objects/resource.rs +++ b/src/kubernetes_api_objects/resource.rs @@ -98,6 +98,7 @@ pub trait ResourceView: Sized { /// This method specifies the validation rule that checks the relations between the new and old object. open spec fn transition_rule(new_obj: Self, old_obj: Self) -> bool; + } } diff --git a/src/kubernetes_cluster/proof/builtin_controllers.rs b/src/kubernetes_cluster/proof/builtin_controllers.rs index b35c5c85f..b9d9384e3 100644 --- a/src/kubernetes_cluster/proof/builtin_controllers.rs +++ b/src/kubernetes_cluster/proof/builtin_controllers.rs @@ -21,6 +21,11 @@ verus! { impl > Cluster { +/// Everytime when we reason about update request message, we can only consider those valid ones (see validata_update_request). +/// However, listing all requirements makes spec looks cumbersome (consider using validate_create/update_request); we can only +/// list those that we need or that may appear according to the spec of system. +/// +/// For example, in some lemma we use msg.content.get_update_request().obj.kind == key.kind, so this requirement is added here. pub open spec fn every_update_msg_sets_owner_references_as( key: ObjectRef, requirements: FnSpec(Option>) -> bool ) -> StatePred { @@ -30,6 +35,7 @@ pub open spec fn every_update_msg_sets_owner_references_as( && msg.dst.is_KubernetesAPI() && msg.content.is_update_request() && msg.content.get_update_request().key == key + && msg.content.get_update_request().obj.kind == key.kind ==> requirements(msg.content.get_update_request().obj.metadata.owner_references) } } diff --git a/src/kubernetes_cluster/proof/message.rs b/src/kubernetes_cluster/proof/message.rs index 99f1e8fc4..6c1c12903 100644 --- a/src/kubernetes_cluster/proof/message.rs +++ b/src/kubernetes_cluster/proof/message.rs @@ -346,6 +346,121 @@ pub proof fn lemma_always_pending_req_has_lower_req_id_than_allocator(spec: Temp ); } +pub open spec fn ok_get_response_msg(key: ObjectRef) -> FnSpec(MsgType) -> bool { + |msg: MsgType| + msg.src.is_KubernetesAPI() + && msg.content.is_get_response() + && msg.content.get_get_response().res.is_Ok() + && msg.content.get_get_response().res.get_Ok_0().object_ref() == key +} + +pub open spec fn object_in_ok_get_response_has_smaller_rv_than_etcd(key: ObjectRef) -> StatePred { + |s: Self| { + let etcd_rv = s.resource_obj_of(key).metadata.resource_version.get_Some_0(); + forall |msg: MsgType| + #[trigger] s.message_in_flight(msg) + && Self::ok_get_response_msg(key)(msg) + ==> msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.is_Some() + && msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter + } +} + +pub proof fn lemma_always_object_in_ok_get_response_has_smaller_rv_than_etcd(spec: TempPred, key: ObjectRef) + requires + spec.entails(lift_state(Self::init())), + spec.entails(always(lift_action(Self::next()))), + ensures + spec.entails(always(lift_state(Self::object_in_ok_get_response_has_smaller_rv_than_etcd(key)))), +{ + let inv = Self::object_in_ok_get_response_has_smaller_rv_than_etcd(key); + let next = |s, s_prime| { + &&& Self::next()(s, s_prime) + &&& Self::each_object_in_etcd_is_well_formed()(s) + &&& Self::each_object_in_etcd_is_well_formed()(s_prime) + &&& Self::each_object_in_reconcile_has_consistent_key_and_valid_metadata()(s) + }; + Self::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(Self::each_object_in_etcd_is_well_formed())); + Self::lemma_always_each_object_in_reconcile_has_consistent_key_and_valid_metadata(spec); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(Self::next()), + lift_state(Self::each_object_in_etcd_is_well_formed()), + later(lift_state(Self::each_object_in_etcd_is_well_formed())), + lift_state(Self::each_object_in_reconcile_has_consistent_key_and_valid_metadata()) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + let etcd_rv = s.resource_obj_of(key).metadata.resource_version.get_Some_0(); + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && Self::ok_get_response_msg(key)(msg) implies + msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.is_Some() + && msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() < s_prime.kubernetes_api_state.resource_version_counter by { + let step = choose |step| Self::next_step(s, s_prime, step); + if s.message_in_flight(msg) { + assert(s.kubernetes_api_state.resource_version_counter <= s_prime.kubernetes_api_state.resource_version_counter); + } else { + let input = step.get_KubernetesAPIStep_0().get_Some_0(); + assert(s.resource_key_exists(input.content.get_get_request().key)); + assert(input.content.get_get_request().key == key); + assert(msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() == s.resource_obj_of(key).metadata.resource_version.get_Some_0()); + assert(s.resource_obj_of(key).metadata.resource_version.get_Some_0() < s.kubernetes_api_state.resource_version_counter); + } + } + } + init_invariant(spec, Self::init(), next, inv); +} + +pub open spec fn object_in_ok_get_resp_is_same_as_etcd_with_same_rv(key: ObjectRef) -> StatePred { + |s: Self| { + forall |msg| + #[trigger] s.message_in_flight(msg) + && Self::ok_get_response_msg(key)(msg) + && s.resource_key_exists(key) + && s.resource_obj_of(key).metadata.resource_version.get_Some_0() == msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() + ==> s.resource_obj_of(key) == msg.content.get_get_response().res.get_Ok_0() + } +} + +pub proof fn lemma_always_object_in_ok_get_resp_is_same_as_etcd_with_same_rv(spec: TempPred, key: ObjectRef) + requires + spec.entails(lift_state(Self::init())), + spec.entails(always(lift_action(Self::next()))), + ensures + spec.entails(always(lift_state(Self::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(key)))), +{ + let inv = Self::object_in_ok_get_resp_is_same_as_etcd_with_same_rv(key); + let next = |s, s_prime| { + &&& Self::next()(s, s_prime) + &&& Self::each_object_in_etcd_is_well_formed()(s) + &&& Self::object_in_ok_get_response_has_smaller_rv_than_etcd(key)(s) + }; + Self::lemma_always_each_object_in_etcd_is_well_formed(spec); + Self::lemma_always_object_in_ok_get_response_has_smaller_rv_than_etcd(spec, key); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(Self::next()), lift_state(Self::each_object_in_etcd_is_well_formed()), + lift_state(Self::object_in_ok_get_response_has_smaller_rv_than_etcd(key)) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + assert forall |msg| #[trigger] s_prime.message_in_flight(msg) && Self::ok_get_response_msg(key)(msg) && s_prime.resource_key_exists(key) + && s_prime.resource_obj_of(key).metadata.resource_version.get_Some_0() == msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0() implies s_prime.resource_obj_of(key) == msg.content.get_get_response().res.get_Ok_0() by { + if s.message_in_flight(msg) { + if !s.resource_key_exists(key) || s.resource_obj_of(key) != s_prime.resource_obj_of(key) { + assert(s_prime.resource_obj_of(key).metadata.resource_version.get_Some_0() != msg.content.get_get_response().res.get_Ok_0().metadata.resource_version.get_Some_0()) + } + } else { + let step = choose |step| Self::next_step(s, s_prime, step); + assert(step.is_KubernetesAPIStep()); + let req = step.get_KubernetesAPIStep_0().get_Some_0(); + assert(msg == Self::handle_get_request(req, s.kubernetes_api_state).1); + assert(s.resource_key_exists(req.content.get_get_request().key)); + assert(msg.content.get_get_response().res.get_Ok_0() == s.resource_obj_of(req.content.get_get_request().key)); + assert(req.content.get_get_request().key == msg.content.get_get_response().res.get_Ok_0().object_ref()); + assert(s.kubernetes_api_state == s_prime.kubernetes_api_state); + assert(s_prime.resource_obj_of(key) == msg.content.get_get_response().res.get_Ok_0()); + } + } + } + init_invariant(spec, Self::init(), next, inv); +} + } } diff --git a/src/kubernetes_cluster/proof/mod.rs b/src/kubernetes_cluster/proof/mod.rs index 3f4b2ff47..b14e10e12 100644 --- a/src/kubernetes_cluster/proof/mod.rs +++ b/src/kubernetes_cluster/proof/mod.rs @@ -11,3 +11,4 @@ pub mod kubernetes_api_liveness; pub mod kubernetes_api_safety; pub mod message; pub mod wf1_assistant; +pub mod validation_rule; diff --git a/src/kubernetes_cluster/proof/validation_rule.rs b/src/kubernetes_cluster/proof/validation_rule.rs new file mode 100644 index 000000000..75608c7d8 --- /dev/null +++ b/src/kubernetes_cluster/proof/validation_rule.rs @@ -0,0 +1,263 @@ +// Copyright 2022 VMware, Inc. +// SPDX-License-Identifier: MIT +#![allow(unused_imports)] +use crate::external_api::spec::*; +use crate::kubernetes_api_objects::{ + api_method::*, common::*, dynamic::*, resource::*, stateful_set::*, +}; +use crate::kubernetes_cluster::spec::{ + cluster::*, + cluster_state_machine::Step, + controller::common::{ControllerActionInput, ControllerStep}, + message::*, +}; +use crate::reconciler::spec::reconciler::Reconciler; +use crate::temporal_logic::{defs::*, rules::*}; +use vstd::prelude::*; + +verus! { + +impl > Cluster { + +pub open spec fn from_dynamic_preserves_spec() -> bool { + forall |d: DynamicObjectView| + #[trigger] K::from_dynamic_object(d).is_Ok() + ==> K::unmarshal_spec(d.spec).get_Ok_0() == K::from_dynamic_object(d).get_Ok_0().spec() +} + +/// The relexitivity allows the metadata to be different. +pub open spec fn is_reflexive_and_transitive() -> bool { + &&& forall |x: K, y: K| x.spec() == y.spec() ==> #[trigger] K::transition_rule(x, y) + &&& forall |x: K, y: K, z: K| #![trigger K::transition_rule(x, y), K::transition_rule(y, z)] + K::transition_rule(x, y) && K::transition_rule(y, z) ==> K::transition_rule(x, z) +} + +/// This spec and also this module are targeted at the relations of the three versions of custom resource object. We know that +/// if cr is updated, the old and new object are subject to the transition rule (if any). Since scheduled_cr and triggering_cr +/// are derived from the cr in etcd, they are either equal to or satisfy the transition rule with etcd cr. +/// +/// When the transition rule is transitive, we can determine a linear order of the three custom resource objects. +pub open spec fn etcd_and_scheduled_and_triggering_cr_in_correct_order(cr: K) -> StatePred { + |s: Self| { + Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr)(s) + && Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)(s) + && Self::triggering_cr_is_in_correct_order_with_etcd_cr(cr)(s) + } +} + +pub proof fn lemma_always_etcd_and_scheduled_and_triggering_cr_in_correct_order(spec: TempPred, cr: K) + requires + K::kind() == Kind::CustomResourceKind, + Self::from_dynamic_preserves_spec(), + Self::is_reflexive_and_transitive(), + spec.entails(lift_state(Self::init())), + spec.entails(always(lift_action(Self::next()))), + ensures + spec.entails(always(lift_state(Self::etcd_and_scheduled_and_triggering_cr_in_correct_order(cr)))), +{ + Self::lemma_always_scheduled_cr_is_in_correct_order_with_etcd_cr(spec, cr); + Self::lemma_always_triggering_cr_is_in_correct_order(spec, cr); + combine_spec_entails_always_n!( + spec, lift_state(Self::etcd_and_scheduled_and_triggering_cr_in_correct_order(cr)), + lift_state(Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr)), + lift_state(Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)), + lift_state(Self::triggering_cr_is_in_correct_order_with_etcd_cr(cr)) + ); +} + +pub open spec fn scheduled_cr_is_in_correct_order_with_etcd_cr(cr: K) -> StatePred { + |s: Self| { + let key = cr.object_ref(); + s.reconcile_scheduled_for(key) + && s.resource_key_exists(key) + && s.resource_obj_of(key).metadata.uid.get_Some_0() == s.reconcile_scheduled_obj_of(key).metadata().uid.get_Some_0() + ==> K::transition_rule(K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0(), s.reconcile_scheduled_obj_of(key)) + } +} + +proof fn lemma_always_scheduled_cr_is_in_correct_order_with_etcd_cr(spec: TempPred, cr: K) + requires + K::kind() == Kind::CustomResourceKind, + Self::is_reflexive_and_transitive(), + Self::from_dynamic_preserves_spec(), + spec.entails(lift_state(Self::init())), + spec.entails(always(lift_action(Self::next()))), + ensures + spec.entails(always(lift_state(Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr)))), +{ + let inv = Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr); + let next = |s, s_prime| { + &&& Self::next()(s, s_prime) + &&& Self::each_object_in_etcd_is_well_formed()(s) + &&& Self::each_object_in_etcd_is_well_formed()(s_prime) + &&& Self::scheduled_cr_has_lower_uid_than_uid_counter()(s) + }; + Self::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(Self::each_object_in_etcd_is_well_formed())); + Self::lemma_always_scheduled_cr_has_lower_uid_than_uid_counter(spec); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(Self::next()), lift_state(Self::each_object_in_etcd_is_well_formed()), + later(lift_state(Self::each_object_in_etcd_is_well_formed())), lift_state(Self::scheduled_cr_has_lower_uid_than_uid_counter()) + ); + let key = cr.object_ref(); + K::object_ref_is_well_formed(); + K::from_dynamic_object_result_determined_by_unmarshal(); + assert forall |s, s_prime: Self| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + if s_prime.reconcile_scheduled_for(key) && s_prime.resource_key_exists(key) + && s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s_prime.reconcile_scheduled_obj_of(key).metadata().uid.get_Some_0() { + let step = choose |step: Step>| Self::next_step(s, s_prime, step); + match step { + Step::KubernetesAPIStep(input) => { + assert(s.reconcile_scheduled_for(key) && s.reconcile_scheduled_obj_of(key) == s_prime.reconcile_scheduled_obj_of(key)); + if !s.resource_key_exists(key) { + assert(s_prime.resource_obj_of(key).metadata.uid == Some(s.kubernetes_api_state.uid_counter)); + assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() != s_prime.reconcile_scheduled_obj_of(key).metadata().uid.get_Some_0()); + } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { + if input.get_Some_0().content.is_delete_request() { + assert(s_prime.resource_obj_of(key).spec == s.resource_obj_of(key).spec); + assert(K::transition_rule( + K::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0(), + K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0() + )); + } else { + assert(input.get_Some_0().content.is_update_request()); + assert(K::from_dynamic_object(input.get_Some_0().content.get_update_request().obj).is_Ok()); + assert(input.get_Some_0().content.get_update_request().obj.spec == s_prime.resource_obj_of(key).spec); + assert(K::transition_rule( + K::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0(), + K::from_dynamic_object(input.get_Some_0().content.get_update_request().obj).get_Ok_0() + )); + } + } + }, + Step::ScheduleControllerReconcileStep(input) => { + assert(s.resource_key_exists(key) && s.resource_obj_of(key) == s_prime.resource_obj_of(key)); + if !s.reconcile_scheduled_for(key) || s.reconcile_scheduled_obj_of(key) != s_prime.reconcile_scheduled_obj_of(key) { + assert(s_prime.reconcile_scheduled_obj_of(key) == K::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0()); + } + }, + _ => {} + } + } + } + init_invariant(spec, Self::init(), next, inv); +} + +pub open spec fn triggering_cr_is_in_correct_order_with_etcd_cr(cr: K) -> StatePred { + |s: Self| { + let key = cr.object_ref(); + s.reconcile_state_contains(key) + && s.resource_key_exists(key) + && s.resource_obj_of(key).metadata.uid.get_Some_0() == s.triggering_cr_of(key).metadata().uid.get_Some_0() + ==> K::transition_rule(K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0(), s.triggering_cr_of(key)) + } +} + +pub open spec fn triggering_cr_is_in_correct_order_with_scheduled_cr(cr: K) -> StatePred { + |s: Self| { + let key = cr.object_ref(); + s.reconcile_state_contains(key) + && s.reconcile_scheduled_for(key) + && s.triggering_cr_of(key).metadata().uid.get_Some_0() == s.reconcile_scheduled_obj_of(key).metadata().uid.get_Some_0() + ==> K::transition_rule(s.reconcile_scheduled_obj_of(key), s.triggering_cr_of(key)) + } +} + +proof fn lemma_always_triggering_cr_is_in_correct_order(spec: TempPred, cr: K) + requires + K::kind() == Kind::CustomResourceKind, + Self::from_dynamic_preserves_spec(), + Self::is_reflexive_and_transitive(), + spec.entails(lift_state(Self::init())), + spec.entails(always(lift_action(Self::next()))), + ensures + spec.entails(always(lift_state(Self::triggering_cr_is_in_correct_order_with_etcd_cr(cr)))), + spec.entails(always(lift_state(Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)))), +{ + let inv = |s: Self| { + &&& Self::triggering_cr_is_in_correct_order_with_etcd_cr(cr)(s) + &&& Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)(s) + }; + let next = |s, s_prime| { + &&& Self::next()(s, s_prime) + &&& Self::each_object_in_etcd_is_well_formed()(s) + &&& Self::each_object_in_etcd_is_well_formed()(s_prime) + &&& Self::triggering_cr_has_lower_uid_than_uid_counter()(s) + &&& Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr)(s) + }; + Self::lemma_always_each_object_in_etcd_is_well_formed(spec); + always_to_always_later(spec, lift_state(Self::each_object_in_etcd_is_well_formed())); + Self::lemma_always_triggering_cr_has_lower_uid_than_uid_counter(spec); + Self::lemma_always_scheduled_cr_is_in_correct_order_with_etcd_cr(spec, cr); + combine_spec_entails_always_n!( + spec, lift_action(next), lift_action(Self::next()), + lift_state(Self::each_object_in_etcd_is_well_formed()), + later(lift_state(Self::each_object_in_etcd_is_well_formed())), + lift_state(Self::triggering_cr_has_lower_uid_than_uid_counter()), + lift_state(Self::scheduled_cr_is_in_correct_order_with_etcd_cr(cr)) + ); + assert forall |s, s_prime| inv(s) && #[trigger] next(s, s_prime) implies inv(s_prime) by { + let key = cr.object_ref(); + K::object_ref_is_well_formed(); + K::from_dynamic_preserves_metadata(); + K::from_dynamic_object_result_determined_by_unmarshal(); + let step = choose |step| Self::next_step(s, s_prime, step); + if s_prime.reconcile_state_contains(key) && s_prime.resource_key_exists(key) + && s_prime.resource_obj_of(key).metadata.uid.get_Some_0() == s_prime.triggering_cr_of(key).metadata().uid.get_Some_0() { + match step { + Step::KubernetesAPIStep(input) => { + assert(s.reconcile_state_contains(key) && s.triggering_cr_of(key) == s_prime.triggering_cr_of(key)); + if !s.resource_key_exists(key) { + assert(s_prime.resource_obj_of(key).metadata.uid == Some(s.kubernetes_api_state.uid_counter)); + assert(s_prime.resource_obj_of(key).metadata.uid.get_Some_0() != s_prime.triggering_cr_of(key).metadata().uid.get_Some_0()); + } else if s.resource_obj_of(key) != s_prime.resource_obj_of(key) { + if input.get_Some_0().content.is_delete_request() { + assert(s_prime.resource_obj_of(key).spec == s.resource_obj_of(key).spec); + assert(K::transition_rule( + K::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0(), + K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0() + )); + } else { + assert(input.get_Some_0().content.is_update_request()); + assert(K::from_dynamic_object(input.get_Some_0().content.get_update_request().obj).is_Ok()); + assert(input.get_Some_0().content.get_update_request().obj.spec == s_prime.resource_obj_of(key).spec); + assert(K::transition_rule( + K::from_dynamic_object(s_prime.resource_obj_of(key)).get_Ok_0(), + K::from_dynamic_object(input.get_Some_0().content.get_update_request().obj).get_Ok_0() + )); + } + } + }, + Step::ControllerStep(_) => { + assert(s.resource_key_exists(key) && s.resource_obj_of(key) == s_prime.resource_obj_of(key)); + if !s.reconcile_state_contains(key) || s.triggering_cr_of(key) != s_prime.triggering_cr_of(key) { + assert(s_prime.triggering_cr_of(key) == s.reconcile_scheduled_obj_of(key)); + } + }, + _ => {} + } + } + if s_prime.reconcile_state_contains(key) && s_prime.reconcile_scheduled_for(key) + && s_prime.triggering_cr_of(key).metadata().uid.get_Some_0() == s_prime.reconcile_scheduled_obj_of(key).metadata().uid.get_Some_0() { + match step { + Step::ScheduleControllerReconcileStep(_) => { + if !s.reconcile_scheduled_for(key) || s.reconcile_scheduled_obj_of(key) != s_prime.reconcile_scheduled_obj_of(key) { + assert(K::transition_rule(s_prime.reconcile_scheduled_obj_of(key), K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0())); + assert(K::transition_rule(K::from_dynamic_object(s.resource_obj_of(key)).get_Ok_0(), s.triggering_cr_of(key))); + } + assert(Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)(s_prime)); + }, + _ => { + assert(Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)(s_prime)); + } + } + } + } + init_invariant(spec, Self::init(), next, inv); + always_weaken(spec, inv, Self::triggering_cr_is_in_correct_order_with_etcd_cr(cr)); + always_weaken(spec, inv, Self::triggering_cr_is_in_correct_order_with_scheduled_cr(cr)); +} + +} + +}