blob: dc1902b964a3dec01c555b0c93149663b908b32a [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{anyhow, Context, Error};
use async_utils::hanging_get::server::{HangingGet, Publisher, Subscriber};
use fidl_fuchsia_power_broker::{
self as fpb, DependencyType, LeaseStatus, Permissions, RegisterDependencyTokenError,
RequiredLevelError, RequiredLevelWatchResponder, StatusError, StatusWatchPowerLevelResponder,
UnregisterDependencyTokenError,
};
use fuchsia_inspect::{InspectType as IType, Node as INode};
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use itertools::Itertools;
use std::cmp::max;
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Debug};
use std::hash::Hash;
use std::iter::repeat;
use uuid::Uuid;
use crate::credentials::*;
use crate::topology::*;
/// Max value for inspect event history.
const INSPECT_GRAPH_EVENT_BUFFER_SIZE: usize = 512;
// Below are a series of type aliases for convenience
type LevelHangingGet<T> =
HangingGet<IndexedPowerLevel, T, Box<dyn Fn(&IndexedPowerLevel, T) -> bool>>;
type LevelSubscriber<T> =
Subscriber<IndexedPowerLevel, T, Box<dyn Fn(&IndexedPowerLevel, T) -> bool>>;
pub type RequiredLevelSubscriber = LevelSubscriber<RequiredLevelWatchResponder>;
pub type CurrentLevelSubscriber = LevelSubscriber<StatusWatchPowerLevelResponder>;
trait Responder<T> {
type Error;
fn send(responder: T, result: Result<u8, Self::Error>) -> Result<(), fidl::Error>;
}
impl Responder<StatusWatchPowerLevelResponder> for StatusWatchPowerLevelResponder {
type Error = StatusError;
fn send(
responder: StatusWatchPowerLevelResponder,
result: Result<u8, StatusError>,
) -> Result<(), fidl::Error> {
responder.send(result)
}
}
impl Responder<RequiredLevelWatchResponder> for RequiredLevelWatchResponder {
type Error = RequiredLevelError;
fn send(
responder: RequiredLevelWatchResponder,
result: Result<u8, RequiredLevelError>,
) -> Result<(), fidl::Error> {
responder.send(result)
}
}
struct LevelAdmin<T> {
/// We pass new power level values to the publisher, which takes care of updating the remote
/// clients using hanging-gets.
publisher: Publisher<IndexedPowerLevel, T, Box<dyn Fn(&IndexedPowerLevel, T) -> bool>>,
/// We use this to vend a new subscriber for each new watch request stream.
hanging_get: HangingGet<IndexedPowerLevel, T, Box<dyn Fn(&IndexedPowerLevel, T) -> bool>>,
/// Cached `IndexedPowerLevel` value. Simply used to determine if the value has changed.
level: IndexedPowerLevel,
}
impl<T: Responder<T>> LevelAdmin<T> {
fn new(initial_level: IndexedPowerLevel) -> Self {
let hanging_get: HangingGet<
IndexedPowerLevel,
T,
Box<dyn Fn(&IndexedPowerLevel, T) -> bool>,
> = LevelHangingGet::<T>::new(
initial_level,
Box::new(|level: &IndexedPowerLevel, res: T| -> bool {
if let Err(error) = T::send(res, Ok(level.level)).context("response failed") {
tracing::warn!(?error, "Failed to send power level to client");
}
true
}),
);
let publisher = hanging_get.new_publisher();
LevelAdmin::<T> { publisher, hanging_get, level: initial_level }
}
}
pub struct Broker {
catalog: Catalog,
credentials: Registry,
// The current level for each element, as reported to the broker.
current: HashMap<ElementID, LevelAdmin<StatusWatchPowerLevelResponder>>,
// The level for each element required by the topology.
required: HashMap<ElementID, LevelAdmin<RequiredLevelWatchResponder>>,
_inspect_node: INode,
}
impl Broker {
pub fn new(inspect: INode) -> Self {
Broker {
catalog: Catalog::new(&inspect),
credentials: Registry::new(),
current: HashMap::new(),
required: HashMap::new(),
_inspect_node: inspect,
}
}
fn lookup_credentials(&self, token: &Token) -> Option<Credential> {
self.credentials.lookup(token)
}
fn unregister_all_credentials_for_element(&mut self, element_id: &ElementID) {
self.credentials.unregister_all_for_element(element_id)
}
#[cfg(test)]
pub fn get_unsatisfiable_element_id(&self) -> ElementID {
self.catalog.topology.get_unsatisfiable_element_id()
}
#[cfg(test)]
pub fn get_unsatisfiable_element_name(&self) -> String {
self.catalog.topology.get_unsatisfiable_element_name()
}
#[cfg(test)]
pub fn get_unsatisfiable_element_levels(&self) -> Vec<u64> {
self.catalog.topology.get_unsatisfiable_element_levels()
}
pub fn register_dependency_token(
&mut self,
element_id: &ElementID,
token: Token,
dependency_type: DependencyType,
) -> Result<(), RegisterDependencyTokenError> {
let permissions = match dependency_type {
DependencyType::Assertive => Permissions::MODIFY_ASSERTIVE_DEPENDENT,
DependencyType::Opportunistic => Permissions::MODIFY_OPPORTUNISTIC_DEPENDENT,
};
match self
.credentials
.register(element_id, CredentialToRegister { broker_token: token, permissions })
{
Err(RegisterCredentialsError::AlreadyInUse) => {
Err(RegisterDependencyTokenError::AlreadyInUse)
}
Err(RegisterCredentialsError::Internal) => Err(RegisterDependencyTokenError::Internal),
Ok(_) => Ok(()),
}
}
pub fn unregister_dependency_token(
&mut self,
element_id: &ElementID,
token: Token,
) -> Result<(), UnregisterDependencyTokenError> {
let Some(credential) = self.lookup_credentials(&token) else {
tracing::debug!("unregister_dependency_token: token not found");
return Err(UnregisterDependencyTokenError::NotFound);
};
if credential.get_element() != element_id {
tracing::debug!(
"unregister_dependency_token: token is registered to {:?}, not {:?}",
&credential.get_element(),
&element_id,
);
return Err(UnregisterDependencyTokenError::NotAuthorized);
}
self.credentials.unregister(&credential);
Ok(())
}
fn current_level_satisfies(&self, required: &ElementLevel) -> bool {
self.get_current_level(&required.element_id)
// If current level is unknown, required is not satisfied.
.is_some_and(|current| current.satisfies(required.level))
}
pub fn update_current_level(&mut self, element_id: &ElementID, level: IndexedPowerLevel) {
tracing::debug!("update_current_level({element_id}, {level:?})");
let prev_level = self.update_current_level_internal(element_id, level);
if prev_level.as_ref() == Some(&level) {
return;
}
if prev_level.is_none() || prev_level.unwrap() < level {
// The level was increased, look for activated assertive or pending
// opportunistic claims that are newly satisfied by the new current level:
tracing::debug!(
"update_current_level({element_id}): level increased from {prev_level:?} to {level:?}"
);
let claims_for_required_element: Vec<Claim> = self
.catalog
.assertive_claims
.activated
.for_required_element(element_id)
.into_iter()
.chain(self.catalog.opportunistic_claims.pending.for_required_element(element_id))
.collect();
tracing::debug!(
"update_current_level({element_id}): claims_satisfied = {})",
&claims_for_required_element.iter().join(", ")
);
// Find claims that are newly satisfied by level:
let claims_satisfied: Vec<Claim> = claims_for_required_element
.into_iter()
.filter(|c| {
level.satisfies(c.requires().level) && !prev_level.satisfies(c.requires().level)
})
.collect();
tracing::debug!(
"update_current_level({element_id}): claims_satisfied = {})",
&claims_satisfied.iter().join(", ")
);
// Find the set of dependents for all claims satisfied:
let dependents_of_claims_satisfied: HashSet<ElementID> =
claims_satisfied.iter().map(|c| c.dependent().element_id.clone()).collect();
// Because at least one of the dependencies of the dependent was
// satisfied, other previously pending assertive claims requiring the
// dependent may now be ready to be activated (though they may not
// if the dependent has other unsatisfied dependencies). Look for
// all pending assertive claims on this dependent, and pass them to
// activate_assertive_claims_if_dependencies_satisfied(), which will check
// if all dependencies of the dependent are now satisfied, and if
// so, activate the pending claims on dependent, raising its
// required level:
for dependent in dependents_of_claims_satisfied {
let pending_assertive_claims_on_dependent =
self.catalog.assertive_claims.pending.for_required_element(&dependent);
tracing::debug!(
"update_current_level({element_id}): pending_assertive_claims_on_dependent({dependent}) = {})",
&pending_assertive_claims_on_dependent.iter().join(", ")
);
self.activate_assertive_claims_if_dependencies_satisfied(
pending_assertive_claims_on_dependent,
);
}
// For pending opportunistic claims, if the current level satisfies them,
// and their lease is no longer contingent, activate the claim.
for claim in self.catalog.opportunistic_claims.pending.for_required_element(element_id)
{
if !level.satisfies(claim.requires().level) {
continue;
}
if self.is_lease_contingent(&claim.lease_id) {
continue;
}
self.catalog.opportunistic_claims.activate_claim(&claim.id)
}
// Find the set of leases for all claims satisfied:
let leases_to_check_if_satisfied: HashSet<LeaseID> =
claims_satisfied.into_iter().map(|c| c.lease_id).collect();
// Update the status of all leases whose claims were satisfied.
tracing::debug!(
"update_current_level({element_id}): leases_to_check_if_satisfied = {:?})",
&leases_to_check_if_satisfied
);
for lease_id in leases_to_check_if_satisfied {
self.update_lease_status(&lease_id);
}
return;
}
if prev_level.unwrap() > level {
// If the level was lowered, find activated claims whose lease has
// become contingent or dropped and see if any of these claims no
// longer have any dependents and thus can be deactivated or
// dropped.
tracing::debug!(
"update_current_level({element_id}): level decreased from {prev_level:?} to {level:?}"
);
let assertive_claims_marked_to_deactivate = self
.catalog
.assertive_claims
.activated
.marked_to_deactivate_for_element(element_id);
let assertive_claims_with_no_dependents =
self.find_claims_with_no_dependents(&assertive_claims_marked_to_deactivate);
let opportunistic_claims_marked_to_deactivate = self
.catalog
.opportunistic_claims
.activated
.marked_to_deactivate_for_element(element_id);
let opportunistic_claims_with_no_dependents =
self.find_claims_with_no_dependents(&opportunistic_claims_marked_to_deactivate);
self.drop_or_deactivate_assertive_claims(&assertive_claims_with_no_dependents);
self.drop_or_deactivate_opportunistic_claims(&opportunistic_claims_with_no_dependents);
}
}
pub fn get_current_level(&self, element_id: &ElementID) -> Option<IndexedPowerLevel> {
self.current.get(element_id).map(|e| e.level)
}
pub fn new_current_level_subscriber(
&mut self,
element_id: &ElementID,
) -> CurrentLevelSubscriber {
self.current
.get_mut(element_id)
.ok_or(anyhow!("Element ({element_id}) not added"))
.unwrap()
.hanging_get
.new_subscriber()
}
fn update_current_level_internal(
&mut self,
element_id: &ElementID,
level: IndexedPowerLevel,
) -> Option<IndexedPowerLevel> {
let previous = self.get_current_level(element_id);
if previous == Some(level) {
return previous;
}
if let Some(current_level) = self.current.get_mut(element_id) {
current_level.publisher.set(level);
current_level.level = level;
} else {
let level_admin = LevelAdmin::<StatusWatchPowerLevelResponder>::new(level);
self.current.insert(element_id.clone(), level_admin);
}
if let Ok(elem_inspect) = self.catalog.topology.inspect_for_element(element_id) {
elem_inspect.borrow_mut().meta().set("current_level", level.level);
}
previous
}
pub fn get_required_level(&self, element_id: &ElementID) -> Option<IndexedPowerLevel> {
self.required.get(element_id).map(|e| e.level)
}
pub fn new_required_level_subscriber(
&mut self,
element_id: &ElementID,
) -> RequiredLevelSubscriber {
self.required
.get_mut(element_id)
.ok_or(anyhow!("Element ({element_id}) not added"))
.unwrap()
.hanging_get
.new_subscriber()
}
fn update_required_level(
&mut self,
element_id: &ElementID,
level: IndexedPowerLevel,
) -> Option<IndexedPowerLevel> {
let previous = self.get_required_level(element_id);
if previous == Some(level) {
return previous;
}
if let Some(required_level) = self.required.get_mut(element_id) {
required_level.publisher.set(level);
required_level.level = level;
} else {
let level_admin = LevelAdmin::<RequiredLevelWatchResponder>::new(level);
self.required.insert(element_id.clone(), level_admin);
}
if let Ok(elem_inspect) = self.catalog.topology.inspect_for_element(element_id) {
elem_inspect.borrow_mut().meta().set("required_level", level.level);
}
previous
}
pub fn acquire_lease(
&mut self,
element_id: &ElementID,
level: IndexedPowerLevel,
) -> Result<Lease, fpb::LeaseError> {
tracing::debug!("acquire_lease({element_id}@{level})");
let (lease, assertive_claims) = self.catalog.create_lease_and_claims(element_id, level);
if self.is_lease_contingent(&lease.id) {
// Lease is blocked on opportunistic claims, update status and return.
tracing::debug!(
"acquire_lease({element_id}@{level}): {} is contingent on opportunistic claims",
&lease.id
);
self.update_lease_status(&lease.id);
return Ok(lease);
}
// Activate all pending assertive claims that have all of their
// dependencies satisfied.
self.activate_assertive_claims_if_dependencies_satisfied(assertive_claims.clone());
// For pending opportunistic claims, if the current level satisfies them,
// and their lease is no longer contingent, activate the claim.
// (If the current level does not satisfy the claim, it will be
// activated as part of update_current_level once it does.)
for claim in self.catalog.opportunistic_claims.pending.for_lease(&lease.id) {
if self.current_level_satisfies(claim.requires()) {
self.catalog.opportunistic_claims.activate_claim(&claim.id)
}
}
self.update_lease_status(&lease.id);
// Other contingent leases may need to update their status if any new
// assertive claims would satisfy their opportunistic claims.
for assertive_claim in assertive_claims {
tracing::debug!(
"check if assertive claim {assertive_claim} would satisfy opportunistic claims"
);
let assertive_claim_requires = &assertive_claim.requires().clone();
let opportunistic_pending_claims_for_req_element = self
.catalog
.opportunistic_claims
.pending
.for_required_element(&assertive_claim_requires.element_id);
let opportunistic_activated_claims_for_req_element = self
.catalog
.opportunistic_claims
.activated
.for_required_element(&assertive_claim_requires.element_id);
let opportunistic_claims_possibly_affected =
opportunistic_pending_claims_for_req_element
.iter()
.chain(opportunistic_activated_claims_for_req_element.iter())
// Only consider claims for leases other than active_claim's
.filter(|c| c.lease_id != lease.id)
// Only consider opportunistic claims that would be satisfied by assertive_claim
.filter(|c| assertive_claim_requires.level.satisfies(c.requires().level));
for opportunistic_claim in opportunistic_claims_possibly_affected {
tracing::debug!(
"assertive claim {assertive_claim} may have changed status of lease {}",
&opportunistic_claim.lease_id
);
if !self.is_lease_contingent(&opportunistic_claim.lease_id) {
tracing::debug!(
"assertive claim {assertive_claim} changed status of lease {}",
&opportunistic_claim.lease_id
);
self.on_lease_transition_to_noncontingent(&opportunistic_claim.lease_id)
}
}
}
Ok(lease)
}
/// Runs when a lease becomes no longer contingent.
fn on_lease_transition_to_noncontingent(&mut self, lease_id: &LeaseID) {
tracing::debug!("on_lease_transition_to_noncontingent({lease_id})");
// Reset any assertive or opportunistic claims that were previously marked to
// deactivate. Since they weren't already deactivated, they must
// already be currently satisfied.
for claim in self.catalog.assertive_claims.activated.for_lease(lease_id) {
self.catalog.assertive_claims.activated.remove_from_claims_to_deactivate(&claim.id)
}
for claim in self.catalog.opportunistic_claims.activated.for_lease(lease_id) {
self.catalog.opportunistic_claims.activated.remove_from_claims_to_deactivate(&claim.id)
}
// Activate any pending assertive claims for this lease whose
// required elements have their dependencies satisfied.
let pending_claims = self.catalog.assertive_claims.pending.for_lease(&lease_id);
self.activate_assertive_claims_if_dependencies_satisfied(pending_claims);
// Activate pending opportunistic claims for this lease, if they are
// (already) currently satisfied.
for claim in self.catalog.opportunistic_claims.pending.for_lease(&lease_id) {
if !self.current_level_satisfies(claim.requires()) {
continue;
}
self.catalog.opportunistic_claims.activate_claim(&claim.id)
}
self.update_lease_status(&lease_id);
}
pub fn drop_lease(&mut self, lease_id: &LeaseID) -> Result<(), Error> {
let (lease, assertive_claims, opportunistic_claims) = self.catalog.drop(lease_id)?;
let assertive_claims_dropped = self.find_claims_with_no_dependents(&assertive_claims);
let opportunistic_claims_dropped =
self.find_claims_with_no_dependents(&opportunistic_claims);
self.drop_or_deactivate_assertive_claims(&assertive_claims_dropped);
self.drop_or_deactivate_opportunistic_claims(&opportunistic_claims_dropped);
self.catalog.lease_status.remove(lease_id);
// Update the required level of the formerly leased element.
self.update_required_levels(&vec![&lease.element_id]);
Ok(())
}
/// Returns true if the lease has one or more opportunistic claims with no assertive
/// claims belonging to other leases that would satisfy them. Such assertive claims
/// must not themselves be contingent. If a lease is contingent on any of its
/// opportunistic claims, none of its claims should be activated.
fn is_lease_contingent(&self, lease_id: &LeaseID) -> bool {
let opportunistic_claims = self
.catalog
.opportunistic_claims
.activated
.for_lease(lease_id)
.into_iter()
.chain(self.catalog.opportunistic_claims.pending.for_lease(lease_id).into_iter());
for claim in opportunistic_claims {
// If there is no other lease with an assertive or pending claim that
// would satisfy each opportunistic claim, the lease is Contingent.
let activated_claims = self
.catalog
.assertive_claims
.activated
.for_required_element(&claim.requires().element_id);
let pending_claims = self
.catalog
.assertive_claims
.pending
.for_required_element(&claim.requires().element_id);
let matching_assertive_claim = activated_claims
.into_iter()
.chain(pending_claims)
// Consider an assertive claim to match if is part of this lease. This captures the
// scenario where a lease is 'self-satisfying' - it holds an assertive claim for an
// element and an opportunistic claim for the same element (at the same or lower
// level).
.filter(|c| lease_id == &c.lease_id || !self.is_lease_contingent(&c.lease_id))
.find(|c| c.requires().level.satisfies(claim.requires().level));
if let Some(matching_assertive_claim) = matching_assertive_claim {
tracing::debug!("{matching_assertive_claim} satisfies opportunistic {claim}");
} else {
return true;
}
}
false
}
fn calculate_lease_status(&self, lease_id: &LeaseID) -> LeaseStatus {
// If the lease has any Pending assertive claims, it is still Pending.
if !self.catalog.assertive_claims.pending.for_lease(lease_id).is_empty() {
return LeaseStatus::Pending;
}
// If the lease has any Pending opportunistic claims, it is still Pending.
if !self.catalog.opportunistic_claims.pending.for_lease(lease_id).is_empty() {
return LeaseStatus::Pending;
}
// If the lease has any opportunistic claims that have not been satisfied
// it is still Pending.
let opportunistic_claims = self.catalog.opportunistic_claims.activated.for_lease(lease_id);
for claim in opportunistic_claims {
if !self.current_level_satisfies(claim.requires()) {
return LeaseStatus::Pending;
}
}
// If the lease is contingent on any opportunistic claims, it is Pending.
// (The opportunistic claims could have been previously satisfied, but then
// an assertive claim was subsequently dropped).
if self.is_lease_contingent(lease_id) {
return LeaseStatus::Pending;
}
// If the lease has any assertive claims that have not been satisfied
// it is still Pending.
for claim in self.catalog.assertive_claims.activated.for_lease(lease_id) {
if !self.current_level_satisfies(claim.requires()) {
return LeaseStatus::Pending;
}
}
// All claims are assertive and satisfied, so the lease is Satisfied.
LeaseStatus::Satisfied
}
// Reevaluates the lease and updates the LeaseStatus.
// Returns the new status if changed, None otherwise.
pub fn update_lease_status(&mut self, lease_id: &LeaseID) -> Option<LeaseStatus> {
let status = self.calculate_lease_status(lease_id);
let prev_status = self.catalog.lease_status.update(lease_id, status);
if prev_status.as_ref() == Some(&status) {
// LeaseStatus was not changed.
return None;
};
// The lease_status changed, update the required level of the leased element.
if let Some(lease) = self.catalog.leases.get(lease_id) {
let elem_id = lease.element_id.clone();
let lease_id = lease.id.clone();
self.update_required_levels(&vec![&elem_id]);
if let Ok(elem_inspect) = self.catalog.topology.inspect_for_element(&elem_id) {
elem_inspect
.borrow_mut()
.meta()
.set_and_track(format!("lease_status_{lease_id}"), format!("{status:?}"));
}
} else {
tracing::warn!("update_lease_status: lease {lease_id} not found");
}
tracing::debug!("update_lease_status({lease_id}) to {status:?}");
// Lease has transitioned from satisfied to pending and contingent.
if prev_status.as_ref() == Some(&LeaseStatus::Satisfied)
&& status == LeaseStatus::Pending
&& self.is_lease_contingent(lease_id)
{
// Mark all activated claims of this lease to be deactivated once
// they are no longer in use.
tracing::debug!(
"update_lease_status({lease_id}): marking activated assertive claims to deactivate"
);
let assertive_claims_to_deactivate =
self.catalog.assertive_claims.activated.mark_to_deactivate(&lease_id);
self.update_required_levels(&element_ids_required_by_claims(
&assertive_claims_to_deactivate,
));
tracing::debug!(
"update_lease_status({lease_id}): marking activated opportunistic claims to deactivate"
);
let opportunistic_claims_to_deactivate =
self.catalog.opportunistic_claims.activated.mark_to_deactivate(&lease_id);
self.update_required_levels(&element_ids_required_by_claims(
&opportunistic_claims_to_deactivate,
));
// Scan assertive claims of this lease. If those assertive claims have any
// opportunistic claims sharing the required element-level of D's assertive claims,
// then re-evaluate their lease to determine if it's newly pending.
for assertive_claim in self.catalog.assertive_claims.activated.for_lease(lease_id) {
let element_of_pending_lease = &assertive_claim.requires().element_id;
for opportunistic_claim in self
.catalog
.opportunistic_claims
.activated
.for_required_element(element_of_pending_lease)
{
if &opportunistic_claim.lease_id != lease_id
&& assertive_claim
.requires()
.level
.satisfies(opportunistic_claim.requires().level)
{
self.update_lease_status(&opportunistic_claim.lease_id);
}
}
}
}
Some(status)
}
#[cfg(test)]
pub fn get_lease_status(&self, lease_id: &LeaseID) -> Option<LeaseStatus> {
self.catalog.get_lease_status(lease_id)
}
pub fn watch_lease_status(
&mut self,
lease_id: &LeaseID,
) -> UnboundedReceiver<Option<LeaseStatus>> {
self.catalog.watch_lease_status(lease_id)
}
fn update_required_levels(&mut self, element_ids: &Vec<&ElementID>) {
for element_id in element_ids {
let new_required_level = self.catalog.calculate_required_level(element_id);
tracing::debug!("update required level({:?}, {:?})", element_id, new_required_level);
self.update_required_level(element_id, new_required_level);
}
}
/// Examines a Vec of pending assertive claims and activates each for which
/// its lease is not contingent on its opportunistic claims and either the
/// required element is already at the required level (and thus the claim
/// is already satisfied) or all of the dependencies of its required
/// ElementLevel are met. Updates required levels of affected elements.
/// For example, let us imagine elements A, B, C and D where A depends on B
/// and B depends on C and D. In order to activate the A->B claim, all
/// dependencies of B (i.e. B->C and B->D) must first be satisfied.
fn activate_assertive_claims_if_dependencies_satisfied(
&mut self,
pending_assertive_claims: Vec<Claim>,
) {
tracing::debug!(
"activate_assertive_claims_if_dependencies_satisfied: pending_assertive_claims[{}]",
pending_assertive_claims.iter().join(", ")
);
// Skip any claims whose leases are still contingent on opportunistic claims.
let contingent_lease_ids: HashSet<LeaseID> = pending_assertive_claims
.iter()
.filter(|c| self.is_lease_contingent(&c.lease_id))
.map(|c| c.lease_id.clone())
.collect();
let claims_to_activate: Vec<Claim> = pending_assertive_claims
.into_iter()
.filter(|c| !contingent_lease_ids.contains(&c.lease_id))
.filter(|c| {
// If the required element is already at the required level,
// then the claim can immediately be activated (and is
// already satisfied).
self.current_level_satisfies(c.requires())
// Otherwise, it can only be activated if all of its
// dependencies are satisfied.
|| self.all_dependencies_satisfied(&c.requires())
})
.collect();
for claim in &claims_to_activate {
self.catalog.assertive_claims.activate_claim(&claim.id);
}
self.update_required_levels(&element_ids_required_by_claims(&claims_to_activate));
}
/// Examines the direct assertive and opportunistic dependencies of an element level
/// and returns true if they are all satisfied (current level >= required).
fn all_dependencies_satisfied(&self, element_level: &ElementLevel) -> bool {
let assertive_dependencies =
self.catalog.topology.direct_assertive_dependencies(&element_level);
let opportunistic_dependencies =
self.catalog.topology.direct_opportunistic_dependencies(&element_level);
assertive_dependencies.into_iter().chain(opportunistic_dependencies).all(|dep| {
if !self.current_level_satisfies(&dep.requires) {
tracing::debug!(
"dependency {dep:?} of element_level {element_level:?} is not satisfied: \
current level of {:?} = {:?}, {:?} required",
&dep.requires.element_id,
self.get_current_level(&dep.requires.element_id),
&dep.requires.level
);
return false;
}
return true;
})
}
/// Examines a Vec of claims and returns any that no longer have any
/// other claims within their lease that require their dependent.
fn find_claims_with_no_dependents(&mut self, claims: &Vec<Claim>) -> Vec<Claim> {
tracing::debug!("find_claims_with_no_dependents: [{}]", claims.iter().join(", "));
let mut claims_to_drop = Vec::new();
for claim_to_check in claims {
let mut has_dependents = false;
// Only claims belonging to the same lease can be a dependent.
for related_claim in
self.catalog.assertive_claims.activated.for_lease(&claim_to_check.lease_id)
{
if related_claim.requires() == claim_to_check.dependent() {
tracing::debug!(
"won't drop {claim_to_check}, has assertive dependent {related_claim}"
);
has_dependents = true;
break;
}
}
if has_dependents {
continue;
}
for related_claim in
self.catalog.opportunistic_claims.activated.for_lease(&claim_to_check.lease_id)
{
if related_claim.requires() == claim_to_check.dependent() {
tracing::debug!(
"won't drop {claim_to_check}, has opportunistic dependent {related_claim}"
);
has_dependents = true;
break;
}
}
if has_dependents {
continue;
}
tracing::debug!("will drop {claim_to_check}");
claims_to_drop.push(claim_to_check.clone());
}
claims_to_drop
}
/// Takes a Vec of assertive claims, deactivates them if their lease is open,
/// or drops them if their lease has been dropped. Then updates lease
/// status of leases affected and required levels of elements affected.
fn drop_or_deactivate_assertive_claims(&mut self, claims: &Vec<Claim>) {
for claim in claims {
tracing::debug!("deactivate assertive claim: {claim}");
if self.catalog.is_lease_dropped(&claim.lease_id) {
self.catalog.assertive_claims.drop_claim(&claim.id);
} else {
self.catalog.assertive_claims.deactivate_claim(&claim.id);
}
}
let element_ids_affected = element_ids_required_by_claims(claims);
self.update_required_levels(&element_ids_affected);
// Update the status of all leases with opportunistic claims no longer satisfied.
let mut leases_affected = HashSet::new();
for element_id in element_ids_affected {
// Calculate the maximum level required by assertive claims only to
// determine if there are still any assertive claims that would
// satisfy these opportunistic claims.
let max_required_by_assertive = max_level_required_by_claims(
&self.catalog.assertive_claims.activated.for_required_element(element_id),
)
.unwrap_or(self.catalog.minimum_level(element_id));
for opportunistic_claim in
self.catalog.opportunistic_claims.activated.for_required_element(element_id)
{
if !max_required_by_assertive.satisfies(opportunistic_claim.requires().level) {
tracing::debug!("opportunistic_claim {opportunistic_claim} no longer satisfied, must reevaluate lease {}", opportunistic_claim.lease_id);
leases_affected.insert(opportunistic_claim.lease_id);
}
}
}
// These leases have opportunistic claims that are no longer satisfied,
// so update_lease_status will update them to pending since they are
// now contingent.
for lease_id in leases_affected {
self.update_lease_status(&lease_id);
}
}
/// Takes a Vec of opportunistic claims, deactivates them if their lease is open,
/// or drops them if their lease has been dropped. Then updates required
/// levels of elements affected.
fn drop_or_deactivate_opportunistic_claims(&mut self, claims: &Vec<Claim>) {
for claim in claims {
if self.catalog.is_lease_dropped(&claim.lease_id) {
tracing::debug!("drop opportunistic claim: {claim}");
self.catalog.opportunistic_claims.drop_claim(&claim.id);
} else {
tracing::debug!("deactivate opportunistic claim: {claim}");
self.catalog.opportunistic_claims.deactivate_claim(&claim.id);
}
}
self.update_required_levels(&element_ids_required_by_claims(claims));
}
pub fn add_element(
&mut self,
name: &str,
initial_current_level: fpb::PowerLevel,
valid_levels: Vec<fpb::PowerLevel>,
level_dependencies: Vec<fpb::LevelDependency>,
assertive_dependency_tokens: Vec<Token>,
opportunistic_dependency_tokens: Vec<Token>,
) -> Result<ElementID, AddElementError> {
if valid_levels.len() < 1 {
return Err(AddElementError::Invalid);
}
let id = self.catalog.topology.add_element(name, valid_levels.to_vec())?;
let initial_current_level = self
.catalog
.topology
.get_level_index(&id, &initial_current_level)
.ok_or(AddElementError::Invalid)?;
self.update_current_level_internal(&id, *initial_current_level);
let minimum_level = self.catalog.topology.minimum_level(&id);
self.update_required_level(&id, minimum_level);
for dependency in level_dependencies {
let requires_token = dependency.requires_token.into();
let Some(requires_cred) = self.lookup_credentials(&requires_token) else {
// Clean up by removing the element we just added.
self.remove_element(&id);
return Err(AddElementError::NotAuthorized);
};
let requires_element_id = requires_cred.get_element();
let requires_level = dependency
.requires_level_by_preference
.iter()
.find_map(|l| self.get_level_index(&requires_element_id, &l))
.ok_or(AddElementError::Invalid)?;
if let Err(err) = self.add_dependency(
&id,
dependency.dependency_type,
dependency.dependent_level,
requires_token,
requires_level.level,
) {
// Clean up by removing the element we just added.
self.remove_element(&id);
return Err(match err {
ModifyDependencyError::AlreadyExists => AddElementError::Invalid,
ModifyDependencyError::Invalid => AddElementError::Invalid,
ModifyDependencyError::NotFound(_) => AddElementError::Invalid,
ModifyDependencyError::NotAuthorized => AddElementError::NotAuthorized,
});
};
}
let labeled_dependency_tokens =
assertive_dependency_tokens.into_iter().zip(repeat(DependencyType::Assertive)).chain(
opportunistic_dependency_tokens
.into_iter()
.zip(repeat(DependencyType::Opportunistic)),
);
for (token, dependency_type) in labeled_dependency_tokens {
if let Err(err) = self.register_dependency_token(&id, token, dependency_type) {
match err {
RegisterDependencyTokenError::Internal => {
tracing::debug!("can't register_dependency_token for {:?}: internal", &id);
return Err(AddElementError::Internal);
}
RegisterDependencyTokenError::AlreadyInUse => {
tracing::debug!(
"can't register_dependency_token for {:?}: already in use",
&id
);
return Err(AddElementError::Invalid);
}
fpb::RegisterDependencyTokenErrorUnknown!() => {
tracing::warn!(
"unknown RegisterDependencyTokenError received: {}",
err.into_primitive()
);
return Err(AddElementError::Internal);
}
}
}
}
Ok(id)
}
#[cfg(test)]
fn element_exists(&self, element_id: &ElementID) -> bool {
self.catalog.topology.element_exists(element_id)
}
pub fn remove_element(&mut self, element_id: &ElementID) {
self.catalog.topology.remove_element(element_id);
self.unregister_all_credentials_for_element(element_id);
self.current.remove(element_id);
self.required.remove(element_id);
}
pub fn get_level_index(
&self,
element_id: &ElementID,
level: &fpb::PowerLevel,
) -> Option<&IndexedPowerLevel> {
self.catalog.topology.get_level_index(element_id, level)
}
/// Checks authorization from requires_token, and if valid, adds an assertive
/// or opportunistic dependency to the Topology, according to dependency_type.
pub fn add_dependency(
&mut self,
element_id: &ElementID,
dependency_type: DependencyType,
dependent_level: fpb::PowerLevel,
requires_token: Token,
requires_level: fpb::PowerLevel,
) -> Result<(), ModifyDependencyError> {
let Some(requires_cred) = self.lookup_credentials(&requires_token) else {
return Err(ModifyDependencyError::NotAuthorized);
};
let dependent_level = self
.catalog
.topology
.get_level_index(&element_id, &dependent_level)
.ok_or(ModifyDependencyError::Invalid)?;
let requires_level = self
.catalog
.topology
.get_level_index(requires_cred.get_element(), &requires_level)
.ok_or(ModifyDependencyError::Invalid)?;
let dependency = Dependency {
dependent: ElementLevel { element_id: element_id.clone(), level: *dependent_level },
requires: ElementLevel {
element_id: requires_cred.get_element().clone(),
level: *requires_level,
},
};
match dependency_type {
DependencyType::Assertive => {
if !requires_cred.contains(Permissions::MODIFY_ASSERTIVE_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.add_assertive_dependency(&dependency)
}
DependencyType::Opportunistic => {
if !requires_cred.contains(Permissions::MODIFY_OPPORTUNISTIC_DEPENDENT) {
return Err(ModifyDependencyError::NotAuthorized);
}
self.catalog.topology.add_opportunistic_dependency(&dependency)
}
}
}
}
pub type LeaseID = String;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct Lease {
pub id: LeaseID,
pub element_id: ElementID,
pub level: IndexedPowerLevel,
}
impl Lease {
fn new(element_id: &ElementID, level: IndexedPowerLevel) -> Self {
let uuid = LeaseID::from(Uuid::new_v4().as_simple().to_string());
let id = if ID_DEBUG_MODE { format!("{element_id}@{level}:{uuid:.6}") } else { uuid };
Lease { id: id.clone(), element_id: element_id.clone(), level: level.clone() }
}
}
type ClaimID = String;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
struct Claim {
pub id: ClaimID,
dependency: Dependency,
pub lease_id: LeaseID,
}
impl fmt::Display for Claim {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Claim{{{}:{:.6}: {}}}", self.lease_id, self.id, self.dependency)
}
}
impl Claim {
fn new(dependency: Dependency, lease_id: &LeaseID) -> Self {
let mut id = ClaimID::from(Uuid::new_v4().as_simple().to_string());
if ID_DEBUG_MODE {
id = format!("{id:.6}");
}
Claim { id, dependency, lease_id: lease_id.clone() }
}
fn dependent(&self) -> &ElementLevel {
&self.dependency.dependent
}
fn requires(&self) -> &ElementLevel {
&self.dependency.requires
}
}
/// Returns a Vec of unique ElementIDs required by claims.
fn element_ids_required_by_claims(claims: &Vec<Claim>) -> Vec<&ElementID> {
claims.into_iter().map(|c| &c.requires().element_id).unique().collect()
}
/// Returns the maximum level required by claims, or None if empty.
fn max_level_required_by_claims(claims: &Vec<Claim>) -> Option<IndexedPowerLevel> {
claims.into_iter().map(|x| x.requires().level).max()
}
#[derive(Debug)]
struct Catalog {
topology: Topology,
leases: HashMap<LeaseID, Lease>,
lease_status: SubscribeMap<LeaseID, LeaseStatus>,
/// Assertive claims can be either Pending or Activated.
/// Pending assertive claims do not yet affect the required levels of their
/// required elements. Some dependencies of their required element are not
/// satisfied.
/// Activated assertive claims affect the required level of the claim's
/// required element.
/// Each assertive claim will start as Pending, and will be Activated once all
/// dependencies of its required element are satisfied.
assertive_claims: ClaimActivationTracker,
///Opportunistic claims can also be either Pending or Activated.
/// Pending opportunistic claims do not affect the required levels of their
/// required elements.
/// Activated opportunistic claims will keep the required level of their
/// required elements from dropping until the lease holder has a chance
/// to drop the lease and release its claims.
/// Each opportunistic claim will start as pending and will be activated when
/// the current level of their required element satisfies their required
/// level. In order for this to happen, an assertive claim from
/// another lease must have been activated and then satisfied.
opportunistic_claims: ClaimActivationTracker,
}
impl Catalog {
fn new(inspect_parent: &INode) -> Self {
Catalog {
topology: Topology::new(
inspect_parent.create_child("topology"),
INSPECT_GRAPH_EVENT_BUFFER_SIZE,
),
leases: HashMap::new(),
lease_status: SubscribeMap::new(Some(inspect_parent.create_child("leases"))),
assertive_claims: ClaimActivationTracker::new(),
opportunistic_claims: ClaimActivationTracker::new(),
}
}
fn minimum_level(&self, element_id: &ElementID) -> IndexedPowerLevel {
self.topology.minimum_level(element_id)
}
/// Returns true if the lease was dropped (or never existed).
fn is_lease_dropped(&self, lease_id: &LeaseID) -> bool {
!self.leases.contains_key(lease_id)
}
/// Calculates the required level for each element, according to the
/// Minimum Power Level Policy.
/// The required level is equal to the maximum of all **activated** assertive
/// or opportunistic claims on the element, the maximum level of all satisfied
/// leases on the element, or the element's minimum level if there are
/// no activated claims or satisfied leases.
fn calculate_required_level(&self, element_id: &ElementID) -> IndexedPowerLevel {
let minimum_level = self.minimum_level(element_id);
let mut activated_claims = self.assertive_claims.activated.for_required_element(element_id);
activated_claims.extend(
self.opportunistic_claims.activated.for_required_element(element_id).into_iter(),
);
max(
max_level_required_by_claims(&activated_claims).unwrap_or(minimum_level),
self.calculate_level_required_by_leases(element_id).unwrap_or(minimum_level),
)
}
/// Calculates the maximum level of all satisfied leases on the element.
fn calculate_level_required_by_leases(
&self,
element_id: &ElementID,
) -> Option<IndexedPowerLevel> {
self.satisfied_leases_for_element(element_id).iter().map(|l| l.level).max()
}
/// Returns all satisfied leases for an element.
fn satisfied_leases_for_element(&self, element_id: &ElementID) -> Vec<Lease> {
// TODO(336609941): Consider optimizing this.
self.leases
.values()
.filter(|l| l.element_id == *element_id)
.filter(|l| self.get_lease_status(&l.id) == Some(LeaseStatus::Satisfied))
.cloned()
.collect()
}
// Given a set of assertive and opportunistic claims, filter out any redundant claims. A claim
// is redundant if there exists another *assertive* claim between the *same pair of elements*
// at an *equal or higher level*.
fn filter_out_redundant_claims(
&self,
redundant_assertive_claims: &Vec<Claim>,
redudnant_opportunistic_claims: &Vec<Claim>,
) -> (Vec<Claim>, Vec<Claim>) {
let mut assertive_claims: Vec<Claim> = redundant_assertive_claims.clone();
let mut opportunistic_claims: Vec<Claim> = redudnant_opportunistic_claims.clone();
let mut essential_assertive_claims: Vec<Claim> = Vec::new();
let mut essential_opportunistic_claims: Vec<Claim> = Vec::new();
let mut observed_pairs: HashMap<(ElementID, ElementID), ElementLevel> = HashMap::new();
assertive_claims.sort_unstable_by_key(|claim| {
(
claim.dependent().element_id.clone(),
claim.requires().element_id.clone(),
usize::MAX - claim.requires().level.index,
)
});
opportunistic_claims.sort_unstable_by_key(|claim| {
(
claim.dependent().element_id.clone(),
claim.requires().element_id.clone(),
usize::MAX - claim.requires().level.index,
)
});
for claim in assertive_claims {
let element_pair =
(claim.dependent().element_id.clone(), claim.requires().element_id.clone());
if observed_pairs.contains_key(&element_pair) {
continue;
} else {
observed_pairs.insert(element_pair, claim.requires().clone());
}
essential_assertive_claims.push(claim.clone());
}
for claim in opportunistic_claims {
let element_pair =
(claim.dependent().element_id.clone(), claim.requires().element_id.clone());
if observed_pairs.contains_key(&element_pair) {
if let Some(requires) = observed_pairs.get(&element_pair) {
if requires.level.satisfies(claim.requires().level) {
continue;
}
}
} else {
observed_pairs.insert(element_pair, claim.requires().clone());
}
essential_opportunistic_claims.push(claim.clone());
}
(essential_assertive_claims, essential_opportunistic_claims)
}
/// Creates a new lease for the given element and level along with all
/// claims necessary to satisfy this lease and adds them to pending_claims.
/// Returns the new lease and the Vec of assertive (pending) claims created.
fn create_lease_and_claims(
&mut self,
element_id: &ElementID,
level: IndexedPowerLevel,
) -> (Lease, Vec<Claim>) {
tracing::debug!("create_lease_and_claims({element_id}@{level})");
// TODO: Add lease validation and control.
let lease = Lease::new(&element_id, level);
if let Ok(elem_inspect) = self.topology.inspect_for_element(element_id) {
let elem_readable_name = self.topology.element_name(element_id);
elem_inspect.borrow_mut().meta().set_and_track(
format!("lease_{}", lease.id),
format!("level_{level}@{elem_readable_name}"), // for example, level_1@elem_name
);
}
self.leases.insert(lease.id.clone(), lease.clone());
let element_level = ElementLevel { element_id: element_id.clone(), level: level.clone() };
let (assertive_dependencies, opportunistic_dependencies) =
self.topology.all_assertive_and_opportunistic_dependencies(&element_level);
// Create all possible claims from the assertive and opportunistic dependencies.
let assertive_claims = assertive_dependencies
.into_iter()
.map(|dependency| Claim::new(dependency.clone(), &lease.id))
.collect::<Vec<Claim>>();
let opportunistic_claims = opportunistic_dependencies
.into_iter()
.map(|dependency| Claim::new(dependency.clone(), &lease.id))
.collect::<Vec<Claim>>();
// Filter claims down to only the essential (i.e. non-redundant) claims.
let (essential_assertive_claims, essential_opportunistic_claims) =
self.filter_out_redundant_claims(&assertive_claims, &opportunistic_claims);
for claim in &essential_assertive_claims {
self.assertive_claims.pending.add(claim.clone());
}
for claim in &essential_opportunistic_claims {
self.opportunistic_claims.pending.add(claim.clone());
}
(lease, essential_assertive_claims)
}
/// Drops an existing lease, and initiates process of releasing all
/// associated claims.
/// Returns the dropped lease, a Vec of assertive claims marked to deactivate,
/// and a Vec of opportunistic claims marked to deactivate.
fn drop(&mut self, lease_id: &LeaseID) -> Result<(Lease, Vec<Claim>, Vec<Claim>), Error> {
tracing::debug!("drop(lease:{lease_id})");
let lease = self.leases.remove(lease_id).ok_or(anyhow!("{lease_id} not found"))?;
self.lease_status.remove(lease_id);
if let Ok(elem_inspect) = self.topology.inspect_for_element(&lease.element_id) {
elem_inspect
.borrow_mut()
.meta()
.remove_and_track(format!("lease_{}", lease.id).as_str());
elem_inspect.borrow_mut().meta().remove(format!("lease_status_{}", lease.id).as_str());
// lease_ drop events are useful as part of understanding lifecycle.
// lease_status_ drop events are redundant with lease_ drops, so we don't record them.
}
tracing::debug!("dropping lease({:?})", &lease);
// Pending claims should be dropped immediately.
let pending_assertive_claims = self.assertive_claims.pending.for_lease(&lease.id);
for claim in pending_assertive_claims {
if let Some(removed) = self.assertive_claims.pending.remove(&claim.id) {
tracing::debug!("removing pending claim: {:?}", &removed);
} else {
tracing::error!("cannot remove pending assertive claim: not found: {}", claim.id);
}
}
let pending_opportunistic_claims = self.opportunistic_claims.pending.for_lease(&lease.id);
for claim in pending_opportunistic_claims {
if let Some(removed) = self.opportunistic_claims.pending.remove(&claim.id) {
tracing::debug!("removing pending opportunistic claim: {:?}", &removed);
} else {
tracing::error!(
"cannot remove pending opportunistic claim: not found: {}",
claim.id
);
}
}
// Assertive andOpportunistic claims should be marked to deactivate in an orderly sequence.
tracing::debug!("drop(lease:{lease_id}): marking activated assertive claims to deactivate");
let assertive_claims_to_deactivate =
self.assertive_claims.activated.mark_to_deactivate(&lease.id);
tracing::debug!(
"drop(lease:{lease_id}): marking activated opportunistic claims to deactivate"
);
let opportunistic_claims_to_deactivate =
self.opportunistic_claims.activated.mark_to_deactivate(&lease.id);
Ok((lease, assertive_claims_to_deactivate, opportunistic_claims_to_deactivate))
}
pub fn get_lease_status(&self, lease_id: &LeaseID) -> Option<LeaseStatus> {
self.lease_status.get(lease_id)
}
fn watch_lease_status(&mut self, lease_id: &LeaseID) -> UnboundedReceiver<Option<LeaseStatus>> {
self.lease_status.subscribe(lease_id)
}
}
/// ClaimActivationTracker divides a set of claims into Pending and Activated
/// states, each of which can separately be accessed as a ClaimLookup.
/// Pending claims have not yet taken effect because of some prerequisite.
/// Activated claims are in effect.
/// For more details on how Pending and Activated are used, see the docs on
/// Catalog above.
#[derive(Debug)]
struct ClaimActivationTracker {
pending: ClaimLookup,
activated: ClaimLookup,
}
impl fmt::Display for ClaimActivationTracker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"pending: [{}], activated: [{}]",
self.pending.claims.values().join(", "),
self.activated.claims.values().join(", ")
)
}
}
impl ClaimActivationTracker {
fn new() -> Self {
Self { pending: ClaimLookup::new(), activated: ClaimLookup::new() }
}
/// Activates a pending claim, moving it to activated.
fn activate_claim(&mut self, claim_id: &ClaimID) {
tracing::debug!("activate_claim: {claim_id}");
self.pending.move_to(&claim_id, &mut self.activated);
}
/// Deactivates an activated claim, moving it to pending.
fn deactivate_claim(&mut self, claim_id: &ClaimID) {
tracing::debug!("deactivate_claim: {claim_id}");
self.activated.move_to(&claim_id, &mut self.pending);
self.activated.remove_from_claims_to_deactivate(&claim_id);
}
/// Removes a claim from both pending and activated.
fn drop_claim(&mut self, claim_id: &ClaimID) {
tracing::debug!("drop_claim: {claim_id}");
self.pending.remove(&claim_id);
self.activated.remove(&claim_id);
}
}
#[derive(Debug)]
struct ClaimLookup {
claims: HashMap<ClaimID, Claim>,
claims_by_required_element_id: HashMap<ElementID, Vec<ClaimID>>,
claims_by_lease: HashMap<LeaseID, Vec<ClaimID>>,
claims_to_deactivate_by_element_id: HashMap<ElementID, Vec<ClaimID>>,
}
impl ClaimLookup {
fn new() -> Self {
Self {
claims: HashMap::new(),
claims_by_required_element_id: HashMap::new(),
claims_by_lease: HashMap::new(),
claims_to_deactivate_by_element_id: HashMap::new(),
}
}
fn add(&mut self, claim: Claim) {
self.claims_by_required_element_id
.entry(claim.requires().element_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
self.claims_by_lease
.entry(claim.lease_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
self.claims.insert(claim.id.clone(), claim);
}
fn remove(&mut self, id: &ClaimID) -> Option<Claim> {
let Some(claim) = self.claims.remove(id) else {
return None;
};
if let Some(claim_ids) =
self.claims_by_required_element_id.get_mut(&claim.requires().element_id)
{
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_by_required_element_id.remove(&claim.requires().element_id);
}
}
if let Some(claim_ids) = self.claims_by_lease.get_mut(&claim.lease_id) {
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_by_lease.remove(&claim.lease_id);
}
}
self.remove_from_claims_to_deactivate(id);
Some(claim)
}
fn remove_from_claims_to_deactivate(&mut self, id: &ClaimID) {
let Some(claim) = self.claims.get(id) else {
return;
};
tracing::debug!("remove_from_claims_to_deactivate: {claim}");
if let Some(claim_ids) =
self.claims_to_deactivate_by_element_id.get_mut(&claim.dependent().element_id)
{
claim_ids.retain(|x| x != id);
if claim_ids.is_empty() {
self.claims_to_deactivate_by_element_id.remove(&claim.dependent().element_id);
}
}
}
/// Marks all claims associated with a lease to deactivate.
/// They will be deactivated in an orderly sequence (each claim will be
/// deactivated only once all claims dependent on it have already been
/// deactivated).
/// Returns a Vec of Claims marked to drop.
fn mark_to_deactivate(&mut self, lease_id: &LeaseID) -> Vec<Claim> {
let claims_marked = self.for_lease(lease_id);
tracing::debug!(
"marking claims to deactivate for lease {lease_id}: [{}]",
&claims_marked.iter().join(", ")
);
for claim in &claims_marked {
self.claims_to_deactivate_by_element_id
.entry(claim.dependent().element_id.clone())
.or_insert(Vec::new())
.push(claim.id.clone());
}
claims_marked
}
/// Removes claim from this lookup, and adds it to recipient.
fn move_to(&mut self, id: &ClaimID, recipient: &mut ClaimLookup) {
if let Some(claim) = self.remove(id) {
recipient.add(claim);
}
}
fn for_claim_ids(&self, claim_ids: &Vec<ClaimID>) -> Vec<Claim> {
claim_ids.iter().map(|id| self.claims.get(id)).filter_map(|f| f).cloned().collect()
}
fn for_required_element(&self, element_id: &ElementID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_by_required_element_id.get(element_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
fn for_lease(&self, lease_id: &LeaseID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_by_lease.get(lease_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
/// Claims with element_id as a dependent that belong to leases which have
/// been dropped. See ClaimLookup::mark_to_deactivate for more details.
fn marked_to_deactivate_for_element(&self, element_id: &ElementID) -> Vec<Claim> {
let Some(claim_ids) = self.claims_to_deactivate_by_element_id.get(element_id) else {
return Vec::new();
};
self.for_claim_ids(claim_ids)
}
}
trait Inspectable {
type Value;
fn track_inspect_with(&self, value: Self::Value, parent: &INode) -> Box<dyn IType>;
}
impl Inspectable for &ElementID {
type Value = IndexedPowerLevel;
fn track_inspect_with(&self, value: Self::Value, parent: &INode) -> Box<dyn IType> {
Box::new(parent.create_uint(self.to_string(), value.level.into()))
}
}
impl Inspectable for &LeaseID {
type Value = LeaseStatus;
fn track_inspect_with(&self, value: Self::Value, parent: &INode) -> Box<dyn IType> {
Box::new(parent.create_string(*self, format!("{:?}", value)))
}
}
#[derive(Debug)]
struct Data<V: Clone + PartialEq> {
value: Option<V>,
senders: Vec<UnboundedSender<Option<V>>>,
_inspect: Option<Box<dyn IType>>,
}
impl<V: Clone + PartialEq> Default for Data<V> {
fn default() -> Self {
Data { value: None, senders: Vec::new(), _inspect: None }
}
}
/// SubscribeMap is a wrapper around a HashMap that stores values V by key K
/// and allows subscribers to register a channel on which they will receive
/// updates whenever the value stored changes.
#[derive(Debug)]
struct SubscribeMap<K: Clone + Hash + Eq, V: Clone + PartialEq> {
values: HashMap<K, Data<V>>,
inspect: Option<INode>,
}
impl<K: Clone + Hash + Eq, V: Clone + PartialEq> SubscribeMap<K, V> {
fn new(inspect: Option<INode>) -> Self {
SubscribeMap { values: HashMap::new(), inspect }
}
fn get(&self, key: &K) -> Option<V> {
self.values.get(key).map(|d| d.value.clone()).flatten()
}
// update updates the value for key.
// Returns previous value, if any.
fn update<'a>(&mut self, key: &'a K, value: V) -> Option<V>
where
&'a K: Inspectable<Value = V>,
V: Copy,
{
let previous = self.get(key);
// If the value hasn't changed, this is a no-op, return.
if previous.as_ref() == Some(&value) {
return previous;
}
let mut senders = Vec::new();
if let Some(Data { value: _, senders: old_senders, _inspect: _ }) = self.values.remove(&key)
{
// Prune invalid senders.
for sender in old_senders {
if let Err(err) = sender.unbounded_send(Some(value.clone())) {
if err.is_disconnected() {
continue;
}
}
senders.push(sender);
}
}
let _inspect = self.inspect.as_mut().map(|inspect| key.track_inspect_with(value, &inspect));
let value = Some(value);
self.values.insert(key.clone(), Data { value, senders, _inspect });
previous
}
fn subscribe(&mut self, key: &K) -> UnboundedReceiver<Option<V>> {
let (sender, receiver) = unbounded::<Option<V>>();
sender.unbounded_send(self.get(key)).expect("initial send should not fail");
self.values.entry(key.clone()).or_default().senders.push(sender);
receiver
}
fn remove(&mut self, key: &K) {
self.values.remove(key);
}
}
/// A IndexedPowerLevel satisfies a required IndexedPowerLevel if it is
/// greater than or equal to it on the same scale.
trait SatisfyPowerLevel {
fn satisfies(&self, required: IndexedPowerLevel) -> bool;
}
impl SatisfyPowerLevel for IndexedPowerLevel {
fn satisfies(&self, required: IndexedPowerLevel) -> bool {
self >= &required
}
}
impl SatisfyPowerLevel for Option<IndexedPowerLevel> {
fn satisfies(&self, required: IndexedPowerLevel) -> bool {
self.is_some() && self.unwrap().satisfies(required)
}
}
#[cfg(test)]
mod tests {
use super::*;
use diagnostics_assertions::{assert_data_tree, AnyProperty};
use fidl_fuchsia_power_broker::{BinaryPowerLevel, DependencyToken};
use fuchsia_zircon::{self as zx, HandleBased};
use lazy_static::lazy_static;
use power_broker_client::BINARY_POWER_LEVELS;
lazy_static! {
static ref TOPOLOGY_UNSATISFIABLE_MAX_LEVEL: String =
format!("{}p", IndexedPowerLevel::MAX.to_string());
}
// Convenience aliases.
const OFF: IndexedPowerLevel =
IndexedPowerLevel { level: BinaryPowerLevel::Off.into_primitive(), index: 0 };
const ON: IndexedPowerLevel =
IndexedPowerLevel { level: BinaryPowerLevel::On.into_primitive(), index: 1 };
const ZERO: IndexedPowerLevel = IndexedPowerLevel::from_same_level_and_index(0);
const ONE: IndexedPowerLevel = IndexedPowerLevel::from_same_level_and_index(1);
const TWO: IndexedPowerLevel = IndexedPowerLevel::from_same_level_and_index(2);
const THREE: IndexedPowerLevel = IndexedPowerLevel::from_same_level_and_index(3);
#[track_caller]
fn assert_lease_cleaned_up(catalog: &Catalog, lease_id: &LeaseID) {
assert!(!catalog.leases.contains_key(lease_id), "{lease_id} still in catalog.leases");
assert!(
catalog.lease_status.get(lease_id).is_none(),
"{lease_id} still in catalog.lease_status"
);
assert_eq!(
catalog.assertive_claims.activated.for_lease(lease_id),
vec![],
"assertive_claims.activated not empty"
);
assert_eq!(
catalog.assertive_claims.pending.for_lease(lease_id),
vec![],
"assertive_claims.pending not empty"
);
assert_eq!(
catalog.opportunistic_claims.activated.for_lease(lease_id),
vec![],
"opportunistic_claims.activated not empty"
);
assert_eq!(
catalog.opportunistic_claims.pending.for_lease(lease_id),
vec![],
"opportunistic_claims.pending not empty"
);
}
#[fuchsia::test]
fn test_binary_satisfy_power_level() {
for (level, required, want) in
[(OFF, ON, false), (OFF, OFF, true), (ON, OFF, true), (ON, ON, true)]
{
let got = level.satisfies(required);
assert_eq!(
got, want,
"{:?}.satisfies({:?}) = {:?}, want {:?}",
level, required, got, want
);
}
}
#[fuchsia::test]
fn test_user_defined_satisfy_power_level() {
for (level, required, want) in [
(0, 1, false),
(0, 0, true),
(1, 0, true),
(1, 1, true),
(255, 0, true),
(255, 1, true),
(255, 255, true),
(1, 255, false),
(35, 36, false),
(35, 35, true),
] {
let level = IndexedPowerLevel::from_same_level_and_index(level);
let required = IndexedPowerLevel::from_same_level_and_index(required);
let got = level.satisfies(required);
assert_eq!(
got, want,
"{:?}.satisfies({:?}) = {:?}, want {:?}",
level, required, got, want
);
}
}
#[fuchsia::test]
fn test_option_satisfy_power_level() {
for (level, required, want) in [
(None, 0, false),
(None, 1, false),
(Some(0), 1, false),
(Some(0), 0, true),
(Some(1), 0, true),
(Some(1), 1, true),
(Some(255), 0, true),
(Some(255), 1, true),
(Some(255), 255, true),
(Some(1), 255, false),
(Some(35), 36, false),
(Some(35), 35, true),
] {
let level = level.map(|l| IndexedPowerLevel::from_same_level_and_index(l));
let required = IndexedPowerLevel::from_same_level_and_index(required);
let got = level.satisfies(required);
assert_eq!(
got, want,
"{:?}.satisfies({:?}) = {:?}, want {:?}",
level, required, got, want
);
}
}
#[fuchsia::test]
fn test_levels() {
let mut levels = SubscribeMap::<ElementID, IndexedPowerLevel>::new(None);
levels.update(&"A".into(), ON);
assert_eq!(levels.get(&"A".into()), Some(ON));
assert_eq!(levels.get(&"B".into()), None);
levels.update(&"A".into(), OFF);
levels.update(&"B".into(), ON);
assert_eq!(levels.get(&"A".into()), Some(OFF));
assert_eq!(levels.get(&"B".into()), Some(ON));
levels.update(&"UD1".into(), IndexedPowerLevel::from_same_level_and_index(145));
assert_eq!(
levels.get(&"UD1".into()),
Some(IndexedPowerLevel::from_same_level_and_index(145))
);
assert_eq!(levels.get(&"UD2".into()), None);
levels.update(&"A".into(), ON);
levels.remove(&"B".into());
assert_eq!(levels.get(&"B".into()), None);
}
#[fuchsia::test]
fn test_levels_subscribe() {
let mut levels = SubscribeMap::<ElementID, IndexedPowerLevel>::new(None);
let mut receiver_a = levels.subscribe(&"A".into());
let mut receiver_b = levels.subscribe(&"B".into());
levels.update(&"A".into(), ON);
assert_eq!(levels.get(&"A".into()), Some(ON));
assert_eq!(levels.get(&"B".into()), None);
levels.update(&"A".into(), OFF);
levels.update(&"B".into(), ON);
assert_eq!(levels.get(&"A".into()), Some(OFF));
assert_eq!(levels.get(&"B".into()), Some(ON));
let mut received_a = Vec::new();
while let Ok(Some(level)) = receiver_a.try_next() {
received_a.push(level)
}
assert_eq!(received_a, vec![None, Some(ON), Some(OFF)]);
let mut received_b = Vec::new();
while let Ok(Some(level)) = receiver_b.try_next() {
received_b.push(level)
}
assert_eq!(received_b, vec![None, Some(ON)]);
}
fn create_test_claim(
dependent_element_id: ElementID,
dependent_element_level: fpb::PowerLevel,
requires_element_id: ElementID,
requires_element_level: fpb::PowerLevel,
) -> Claim {
Claim::new(
Dependency {
dependent: ElementLevel {
element_id: dependent_element_id,
level: IndexedPowerLevel::from_same_level_and_index(dependent_element_level),
},
requires: ElementLevel {
element_id: requires_element_id,
level: IndexedPowerLevel::from_same_level_and_index(requires_element_level),
},
},
&LeaseID::new(),
)
}
#[fuchsia::test]
fn test_filter_out_redundant_claims() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let broker = Broker::new(inspect_node);
let claim_a_1_b_1 = create_test_claim("A".into(), 1, "B".into(), 1);
let claim_a_2_b_2 = create_test_claim("A".into(), 2, "B".into(), 2);
let claim_a_1_c_1 = create_test_claim("A".into(), 1, "C".into(), 1);
let claim_b_1_c_1 = create_test_claim("B".into(), 1, "C".into(), 1);
let claim_a_2_c_2 = create_test_claim("A".into(), 2, "C".into(), 2);
// A B
// 1 ==> 1 (redundant with A@2=>B@2)
// 2 ==> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone(), claim_a_2_b_2.clone()],
&vec![],
);
assert_eq!(essential_assertive_claims, vec![claim_a_2_b_2.clone()]);
assert_eq!(essential_opportunistic_claims, vec![]);
// A B
// 1 --> 1 (redundant with A@2=>B@2)
// 2 ==> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_2_b_2.clone()],
&vec![claim_a_1_b_1.clone()],
);
assert_eq!(essential_assertive_claims, vec![claim_a_2_b_2.clone()]);
assert_eq!(essential_opportunistic_claims, vec![]);
// A B
// 1 ==> 1 (not redundant, opportunistic claims cannot satisfy assertive claims)
// 2 --> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone()],
&vec![claim_a_2_b_2.clone()],
);
assert_eq!(essential_assertive_claims, vec![claim_a_1_b_1.clone()]);
assert_eq!(essential_opportunistic_claims, vec![claim_a_2_b_2.clone()]);
// A B
// 1 --> 1 (redundant with A@2->B@2)
// 2 --> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![],
&vec![claim_a_1_b_1.clone(), claim_a_2_b_2.clone()],
);
assert_eq!(essential_assertive_claims, vec![]);
assert_eq!(essential_opportunistic_claims, vec![claim_a_2_b_2.clone()]);
// A B C
// 1 ========> 1 (not redundant, not between same elements)
// 2 ==> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_c_1.clone(), claim_a_2_b_2.clone()],
&vec![],
);
assert_eq!(essential_assertive_claims, vec![claim_a_2_b_2.clone(), claim_a_1_c_1.clone()]);
assert_eq!(essential_opportunistic_claims, vec![]);
// A B C
// 1 --------> 1 (not redundant, not between same elements)
// 2 --> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![],
&vec![claim_a_1_c_1.clone(), claim_a_2_b_2.clone()],
);
assert_eq!(essential_assertive_claims, vec![]);
assert_eq!(
essential_opportunistic_claims,
vec![claim_a_2_b_2.clone(), claim_a_1_c_1.clone()]
);
// A B C
// 1 ==> 1 ==> 1 (not redundant, A@2=>C@2 cannot satisfy B@1=>C@1, not between same elements)
// 2 ========> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone(), claim_b_1_c_1.clone(), claim_a_2_c_2.clone()],
&vec![],
);
assert_eq!(
essential_assertive_claims,
vec![claim_a_1_b_1.clone(), claim_a_2_c_2.clone(), claim_b_1_c_1.clone()]
);
assert_eq!(essential_opportunistic_claims, vec![]);
// A B C
// 1 ==> 1 --> 1 (not redundant, A@2=>C@2 - B@1->C@1, not between same elements)
// 2 ========> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone(), claim_a_2_c_2.clone()],
&vec![claim_b_1_c_1.clone()],
);
assert_eq!(essential_assertive_claims, vec![claim_a_1_b_1.clone(), claim_a_2_c_2.clone()]);
assert_eq!(essential_opportunistic_claims, vec![claim_b_1_c_1.clone()]);
// A B C
// 1 ==> 1 ==> 1 (not redundant, A@2->C@2 - B@1=>C@1, not between same elements)
// 2 --------> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone(), claim_b_1_c_1.clone()],
&vec![claim_a_2_c_2.clone()],
);
assert_eq!(essential_assertive_claims, vec![claim_a_1_b_1.clone(), claim_b_1_c_1.clone()]);
assert_eq!(essential_opportunistic_claims, vec![claim_a_2_c_2.clone()]);
// A B C
// 1 ==> 1 --> 1 (not redundant, A@2->C@2 - B@1->C@1, not between same elements)
// 2 --------> 2
let (essential_assertive_claims, essential_opportunistic_claims) =
broker.catalog.filter_out_redundant_claims(
&vec![claim_a_1_b_1.clone()],
&vec![claim_b_1_c_1.clone(), claim_a_2_c_2.clone()],
);
assert_eq!(essential_assertive_claims, vec![claim_a_1_b_1.clone()]);
assert_eq!(
essential_opportunistic_claims,
vec![claim_a_2_c_2.clone(), claim_b_1_c_1.clone()]
);
}
#[fuchsia::test]
fn test_initialize_current_and_broker_status() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let latinum = broker
.add_element(
"Latinum",
7,
// Unsorted. The order declares the order of increasing power.
vec![5, 2, 7],
vec![],
vec![],
vec![],
)
.expect("add_element failed");
assert_eq!(
broker.get_current_level(&latinum),
Some(IndexedPowerLevel { level: 7, index: 2 })
);
assert_eq!(
broker.get_required_level(&latinum),
Some(IndexedPowerLevel { level: 5, index: 0 })
);
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
latinum.to_string() => {
meta: {
name: "Latinum",
valid_levels: vec![5u64, 2u64, 7u64],
current_level: 7u64,
required_level: 5u64,
},
relationships: {},
},
},
events: {
"0": {
"@time": AnyProperty,
vertex_id: broker.get_unsatisfiable_element_id().to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"1": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"2": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "current_level",
update: 7u64,
},
"3": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "required_level",
update: 5u64,
},
},
}}}});
}
#[fuchsia::test]
fn test_current_required_level_inspect() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let latinum = broker
.add_element("Latinum", 2, vec![0, 1, 2], vec![], vec![], vec![])
.expect("add_element failed");
assert_eq!(broker.get_current_level(&latinum), Some(TWO));
assert_eq!(broker.get_required_level(&latinum), Some(ZERO));
// Update required level to 1.
broker.update_required_level(&latinum, ONE);
// Update required level to 1 again, should have no additional effect.
broker.update_required_level(&latinum, ONE);
// Update current level to 1.
broker.update_current_level(&latinum, ONE);
// Update current level to 1 again, should have no additional effect.
broker.update_current_level(&latinum, ONE);
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
latinum.to_string() => {
meta: {
name: "Latinum",
valid_levels: vec![0u64, 1u64, 2u64],
current_level: 1u64,
required_level: 1u64,
},
relationships: {},
},
},
events: {
"0": {
"@time": AnyProperty,
vertex_id: broker.get_unsatisfiable_element_id().to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"1": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"2": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "current_level",
update: 2u64,
},
"3": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "required_level",
update: 0u64,
},
"4": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "required_level",
update: 1u64,
},
"5": {
"@time": AnyProperty,
vertex_id: latinum.to_string(),
event: "update_key",
key: "current_level",
update: 1u64,
},
},
}}}});
}
#[fuchsia::test]
fn test_add_element_dependency_never_and_unregistered() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_mithril = DependencyToken::create();
let never_registered_token = DependencyToken::create();
let mithril = broker
.add_element(
"Mithril",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let v01: Vec<u64> = BINARY_POWER_LEVELS.iter().map(|&v| v as u64).collect();
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
mithril.to_string() => {
meta: {
name: "Mithril",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
},
"events": contains {},
},
},
}});
// This should fail, because the token was never registered.
let add_element_not_authorized_res = broker.add_element(
"Silver",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: never_registered_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
);
assert!(matches!(add_element_not_authorized_res, Err(AddElementError::NotAuthorized)));
// Add element with a valid token should succeed.
let silver = broker
.add_element(
"Silver",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
mithril.to_string() => {
meta: {
name: "Mithril",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
silver.to_string() => {
meta: {
name: "Silver",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {
mithril.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
},
},
},
"events": contains {},
}}}});
// Unregister token_mithril, then try to add again, which should fail.
broker
.unregister_dependency_token(
&mithril,
token_mithril.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup failed").into(),
)
.expect("unregister_dependency_token failed");
let add_element_not_authorized_res: Result<ElementID, AddElementError> = broker
.add_element(
"Silver",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
);
assert!(matches!(add_element_not_authorized_res, Err(AddElementError::NotAuthorized)));
}
#[fuchsia::test]
fn test_remove_element() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let unobtanium = broker
.add_element(
"Unobtainium",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![],
vec![],
)
.expect("add_element failed");
assert_eq!(broker.element_exists(&unobtanium), true);
let v01: Vec<u64> = BINARY_POWER_LEVELS.iter().map(|&v| v as u64).collect();
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
unobtanium.to_string() => {
meta: {
name: "Unobtainium",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
},
events: {
"0": {
"@time": AnyProperty,
vertex_id: broker.get_unsatisfiable_element_id().to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"1": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"2": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "update_key",
key: "current_level",
update: OFF.level as u64,
},
"3": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "update_key",
key: "required_level",
update: OFF.level as u64,
},
},
},
}}});
broker.remove_element(&unobtanium);
assert_eq!(broker.element_exists(&unobtanium), false);
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
},
events: {
"0": {
"@time": AnyProperty,
vertex_id: broker.get_unsatisfiable_element_id().to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"1": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "add_vertex",
meta: {
current_level: "unset",
required_level: "unset",
},
},
"2": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "update_key",
key: "current_level",
update: OFF.level as u64,
},
"3": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "update_key",
key: "required_level",
update: OFF.level as u64,
},
"4": {
"@time": AnyProperty,
vertex_id: unobtanium.to_string(),
event: "remove_vertex",
},
},
}}}});
}
struct BrokerStatusMatcher {
lease: LeaseMatcher,
required_level: RequiredLevelMatcher,
}
impl BrokerStatusMatcher {
fn new() -> Self {
Self { lease: LeaseMatcher::new(), required_level: RequiredLevelMatcher::new() }
}
#[track_caller]
fn assert_matches(&self, broker: &Broker) {
self.lease.assert_matches(broker);
self.required_level.assert_matches(broker);
}
}
struct RequiredLevelMatcher {
elements: HashMap<ElementID, IndexedPowerLevel>,
}
impl RequiredLevelMatcher {
fn new() -> Self {
Self { elements: HashMap::new() }
}
fn update(&mut self, id: &ElementID, required_level: IndexedPowerLevel) {
self.elements.insert(id.clone(), required_level);
}
#[track_caller]
fn assert_matches(&self, broker: &Broker) {
for (id, expected) in &self.elements {
let rl = broker.get_required_level(id).unwrap();
assert_eq!(rl, *expected, "get_required_level({id}) = {rl}, expected = {expected}");
}
}
}
struct LeaseMatcher {
leases: HashMap<LeaseID, (LeaseStatus, bool)>,
}
impl LeaseMatcher {
fn new() -> Self {
Self { leases: HashMap::new() }
}
fn update(&mut self, id: &LeaseID, status: LeaseStatus, contingent: bool) {
self.leases.insert(id.clone(), (status, contingent));
}
fn remove(&mut self, id: &LeaseID) {
self.leases.remove(id);
}
#[track_caller]
fn assert_matches(&self, broker: &Broker) {
for (id, (expected_status, expected_contingent)) in &self.leases {
let status = broker.get_lease_status(id).unwrap();
let contingent = broker.is_lease_contingent(id);
assert_eq!(
status, *expected_status,
"get_lease_status({id}) = {status:?}, expected = {expected_status:?}"
);
assert_eq!(
contingent, *expected_contingent,
"is_lease_contingent({id}) = {contingent}, expected = {expected_contingent}"
);
}
}
}
#[fuchsia::test]
fn test_broker_lease_direct() {
// Create a topology of a child element with two direct assertive dependencies.
// P1 <= C => P2
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let parent1_token = DependencyToken::create();
let parent1: ElementID = broker
.add_element(
"P1",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent1_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent2_token = DependencyToken::create();
let parent2: ElementID = broker
.add_element(
"P2",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent2_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: parent1_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: parent2_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// All elements should start with required level OFF.
broker_status.required_level.update(&parent1, OFF);
broker_status.required_level.update(&parent2, OFF);
broker_status.required_level.update(&child, OFF);
broker_status.assert_matches(&broker);
let v01: Vec<u64> = BINARY_POWER_LEVELS.iter().map(|&v| v as u64).collect();
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
parent1.to_string() => {
meta: {
name: "P1",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
parent2.to_string() => {
meta: {
name: "P2",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
child.to_string() => {
meta: {
name: "C",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {
parent1.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
parent2.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
},
},
},
events: contains {},
},
}}});
// Acquire the lease, which should result in two claims, one
// for each dependency.
let lease = broker.acquire_lease(&child, ON).expect("acquire failed");
// Parent 1's required level should become ON from direct claim
broker_status.required_level.update(&parent1, ON);
// Parent 2's required level should become ON from direct claim
broker_status.required_level.update(&parent2, ON);
// The lease should be Pending.
broker_status.lease.update(&lease.id, LeaseStatus::Pending, false);
broker_status.assert_matches(&broker);
// Update P1's current level to ON.
// The lease should still be Pending.
broker.update_current_level(&parent1, ON);
// No required levels should change.
broker_status.assert_matches(&broker);
// Update P2's current level to ON.
// The lease should now be Satisfied.
broker.update_current_level(&parent2, ON);
// Child's required level should become ON
broker_status.required_level.update(&child, ON);
broker_status.lease.update(&lease.id, LeaseStatus::Satisfied, false);
broker_status.assert_matches(&broker);
assert_data_tree!(inspect, root: {
test: {
leases: {
lease.id.clone() => "Satisfied",
},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
parent1.to_string() => {
meta: {
name: "P1",
valid_levels: v01.clone(),
current_level: ON.level as u64,
required_level: ON.level as u64,
},
relationships: {},
},
parent2.to_string() => {
meta: {
name: "P2",
valid_levels: v01.clone(),
current_level: ON.level as u64,
required_level: ON.level as u64,
},
relationships: {},
},
child.to_string() => {
meta: {
name: "C",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: ON.level as u64,
format!("lease_{}", lease.id) => "level_1@C",
format!("lease_status_{}", lease.id) => "Satisfied",
},
relationships: {
parent1.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
parent2.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
},
},
},
events: contains {
"14": {
"@time": AnyProperty,
event: "update_key",
key: format!("lease_{}", lease.id),
update: "level_1@C",
vertex_id: child.to_string()
},
"17": {
"@time": AnyProperty,
event: "update_key",
key: format!("lease_status_{}", lease.id),
update: "Pending",
vertex_id: child.to_string()
},
"21": {
"@time": AnyProperty,
event: "update_key",
key: format!("lease_status_{}", lease.id),
update: "Satisfied",
vertex_id: child.to_string()
}
},
},
}}});
// Update Child's current level to ON.
broker.update_current_level(&child, ON);
// No required levels should change.
broker_status.assert_matches(&broker);
// Now drop the lease and verify both claims are also dropped.
broker.drop_lease(&lease.id).expect("drop failed");
// Parent 1's required level should become OFF from dropped claim
broker_status.required_level.update(&parent1, OFF);
// Parent 2's required level should become OFF from dropped claim
broker_status.required_level.update(&parent2, OFF);
// Child's required level should become OFF
broker_status.required_level.update(&child, OFF);
broker_status.lease.remove(&lease.id);
broker_status.assert_matches(&broker);
// Try dropping the lease one more time, which should result in an error.
let extra_drop = broker.drop_lease(&lease.id);
assert!(extra_drop.is_err());
assert_lease_cleaned_up(&broker.catalog, &lease.id);
}
#[fuchsia::test]
fn test_broker_lease_transitive() {
// Create a topology of a child element with two chained transitive
// dependencies.
// C -> P -> GP
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let grandparent_token = DependencyToken::create();
let grandparent: ElementID = broker
.add_element(
"GP",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent_token = DependencyToken::create();
let parent: ElementID = broker
.add_element(
"P",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
broker
.add_dependency(
&parent,
DependencyType::Assertive,
ON.level,
grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
ON.level,
)
.expect("add_dependency failed");
let mut broker_status = BrokerStatusMatcher::new();
// All elements should start with required level OFF.
broker_status.required_level.update(&parent, OFF);
broker_status.required_level.update(&grandparent, OFF);
broker_status.required_level.update(&child, OFF);
broker_status.assert_matches(&broker);
// Acquire the lease, which should result in two claims, one
// for the direct parent dependency, and one for the transitive
// grandparent dependency.
let lease = broker.acquire_lease(&child, ON).expect("acquire failed");
// Parent's required level should remain OFF, waiting on Grandparent to turn ON
// Grandparent's required level should become ON because it has no dependencies
broker_status.required_level.update(&grandparent, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease.id), Some(LeaseStatus::Pending));
// Raise Grandparent power level to ON, now Parent claim should be enforced.
broker.update_current_level(&grandparent, ON);
assert_eq!(broker.get_required_level(&parent).unwrap(), ON,);
// Parent's required level should become ON because Grandparent is now ON.
broker_status.required_level.update(&parent, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease.id), Some(LeaseStatus::Pending));
// Raise Parent power level to ON, now lease should be Satisfied.
broker.update_current_level(&parent, ON);
// Child's required level should become ON now that the lease is satisfied.
broker_status.required_level.update(&child, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease.id), Some(LeaseStatus::Satisfied));
// Raise Child power level to ON.
// All required levels should still be ON.
// Lease should still be Satisfied.
broker.update_current_level(&parent, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease.id), Some(LeaseStatus::Satisfied));
// Now drop the lease and verify Parent claim is dropped, but
// Grandparent claim is not yet dropped.
broker.drop_lease(&lease.id).expect("drop failed");
// Parent's required level should become OFF after lease drop
// Grandparent's required level should remain ON
// Child's required level should become OFF.
broker_status.required_level.update(&parent, OFF);
broker_status.required_level.update(&child, OFF);
broker_status.assert_matches(&broker);
// Lower Parent power level to OFF, now Grandparent claim should be
// dropped and should have required level OFF.
broker.update_current_level(&parent, OFF);
broker_status.required_level.update(&grandparent, OFF);
broker_status.assert_matches(&broker);
assert_lease_cleaned_up(&broker.catalog, &lease.id);
}
#[fuchsia::test]
fn test_broker_lease_shared() {
// Create a topology of two child elements with a shared
// parent and grandparent
// C1 \
// > P -> GP
// C2 /
// Child 1 requires Parent at 50 to support its own level of 5.
// Parent requires Grandparent at 200 to support its own level of 50.
// C1 -> P -> GP
// 5 -> 50 -> 200
// Child 2 requires Parent at 30 to support its own level of 3.
// Parent requires Grandparent at 90 to support its own level of 30.
// C2 -> P -> GP
// 3 -> 30 -> 90
// Grandparent has a minimum required level of 10.
// All other elements have a minimum of 0.
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let grandparent_token = DependencyToken::create();
let grandparent: ElementID = broker
.add_element(
"GP",
10,
vec![10, 90, 200],
vec![],
vec![grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let parent_token = DependencyToken::create();
let parent: ElementID = broker
.add_element(
"P",
0,
vec![0, 30, 50],
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 50,
requires_token: grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![200],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 30,
requires_token: grandparent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![90],
},
],
vec![parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let child1 = broker
.add_element(
"C1",
0,
vec![0, 5],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 5,
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![50],
}],
vec![],
vec![],
)
.expect("add_element failed");
let child2 = broker
.add_element(
"C2",
0,
vec![0, 3],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 3,
requires_token: parent_token
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![30],
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initially, all elements should be at their default required levels.
// Grandparent should have a default required level of 10
// and all others should have a default required level of 0.
let mut broker_status = BrokerStatusMatcher::new();
broker_status.required_level.update(&parent, ZERO);
broker_status
.required_level
.update(&grandparent, IndexedPowerLevel { level: 10, index: 0 });
broker_status.required_level.update(&child1, ZERO);
broker_status.required_level.update(&child2, ZERO);
broker_status.assert_matches(&broker);
// Acquire lease for Child 1. Initially, Grandparent should have
// required level 200 and Parent should have required level 0
// because Child 1 has a dependency on Parent and Parent has a
// dependency on Grandparent. Grandparent has no dependencies so its
// level should be raised first.
let lease1 = broker
.acquire_lease(&child1, IndexedPowerLevel { level: 5, index: 1 })
.expect("acquire failed");
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
0,
"Parent's required level should become 0, waiting on Grandparent to reach required level"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
200,
"Grandparent's required level should become 100 because of parent dependency and it has no dependencies of its own"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should remain 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
0,
"Child 2's required level should remain 0"
);
assert_eq!(broker.get_lease_status(&lease1.id), Some(LeaseStatus::Pending));
// Raise Grandparent's current level to 200. Now Parent claim should
// be enforced, because its dependency on Grandparent is unblocked
// raising its required level to 50.
broker.update_current_level(&grandparent, IndexedPowerLevel { level: 200, index: 2 });
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
50,
"Parent's required level should become 50"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
200,
"Grandparent's required level should remain 200"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should remain 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
0,
"Child 2's required level should remain 0"
);
assert_eq!(broker.get_lease_status(&lease1.id), Some(LeaseStatus::Pending));
// Update Parent's current level to 50.
// Parent and Grandparent should have required levels of 50 and 200.
broker.update_current_level(&parent, IndexedPowerLevel { level: 50, index: 2 });
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
50,
"Parent's required level should become 50"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
200,
"Grandparent's required level should remain 200"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
5,
"Child 1's required level should become 5"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
0,
"Child 2's required level should remain 0"
);
assert_eq!(broker.get_lease_status(&lease1.id), Some(LeaseStatus::Satisfied));
// Acquire a lease for Child 2. Though Child 2 has nominal
// requirements of Parent at 30 and Grandparent at 100, they are
// superseded by Child 1's requirements of 50 and 200.
let lease2 = broker
.acquire_lease(&child2, IndexedPowerLevel { level: 3, index: 1 })
.expect("acquire failed");
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
50,
"Parent's required level should remain 50"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
200,
"Grandparent's required level should remain 200"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
5,
"Child 1's required level should remain 5"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
3,
"Child 2's required level should become 3"
);
assert_eq!(broker.get_lease_status(&lease1.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease2.id), Some(LeaseStatus::Satisfied));
// Drop lease for Child 1. Parent's required level should immediately
// drop to 30. Grandparent's required level will remain at 200 for now.
broker.drop_lease(&lease1.id).expect("drop failed");
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
30,
"Parent's required level should drop to 30"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
200,
"Grandparent's required level should remain 200"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should become 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
3,
"Child 2's required level should remain 3"
);
assert_eq!(broker.get_lease_status(&lease2.id), Some(LeaseStatus::Satisfied));
// Lower Parent's current level to 30. Now Grandparent's required level
// should drop to 90.
broker.update_current_level(&parent, IndexedPowerLevel { level: 30, index: 1 });
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
30,
"Parent should have required level 30"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
90,
"Grandparent's required level should become 90"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should become 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
3,
"Child 2's required level should remain 3"
);
assert_eq!(broker.get_lease_status(&lease2.id), Some(LeaseStatus::Satisfied));
// Drop lease for Child 2, Parent should have required level 0.
// Grandparent's required level should remain 90.
broker.drop_lease(&lease2.id).expect("drop failed");
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
0,
"Parent's required level should become 0"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
90,
"Grandparent's required level should remain 90"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should become 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
0,
"Child 2's required level should become 0"
);
// Lower Parent's current level to 0. Grandparent claim should now be
// dropped and have its default required level of 10.
broker.update_current_level(&parent, ZERO);
assert_eq!(
broker.get_required_level(&parent).unwrap().level,
0,
"Parent should have required level 0"
);
assert_eq!(
broker.get_required_level(&grandparent).unwrap().level,
10,
"Grandparent's required level should become 10"
);
assert_eq!(
broker.get_required_level(&child1).unwrap().level,
0,
"Child 1's required level should remain 0"
);
assert_eq!(
broker.get_required_level(&child2).unwrap().level,
0,
"Child 2's required level should remain 0"
);
assert_lease_cleaned_up(&broker.catalog, &lease1.id);
assert_lease_cleaned_up(&broker.catalog, &lease2.id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_direct() {
// Tests that a lease with an opportunistic claim is Contingent while there
// are no other leases with assertive claims that would satisfy its
// opportunistic claim.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// A B C
// ON <= ON
// ON <------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required levels for all elements should be OFF.
// Set all current levels to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
// Lease C.
// A's required level should remain OFF because of C's opportunistic claim.
// B's required level should remain OFF.
// C's required level should remain OFF because the lease is still Pending.
// Lease C should be pending and contingent on its opportunistic claim.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B's required level should remain OFF because the lease is still Pending.
// C's required level should remain OFF because the lease is still Pending.
// Lease B should be pending.
// Lease C should remain pending but is no longer contingent because
// B's assertive claim unblocks C's opportunistic claim.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because the lease is Satisfied.
// C's required level should become ON because the lease is Satisfied.
// Lease B & C should become satisfied.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.required_level.update(&element_c, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
let v01: Vec<u64> = BINARY_POWER_LEVELS.iter().map(|&v| v as u64).collect();
assert_data_tree!(inspect, root: {
test: {
leases: {
lease_b_id.clone() => "Satisfied",
lease_c_id.clone() => "Satisfied",
},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
element_a.to_string() => {
meta: {
name: "A",
valid_levels: v01.clone(),
current_level: ON.level as u64,
required_level: ON.level as u64,
},
relationships: {},
},
element_b.to_string() => {
meta: {
name: "B",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: ON.level as u64,
format!("lease_{}", lease_b_id) => "level_1@B",
format!("lease_status_{}", lease_b_id) => "Satisfied",
},
relationships: {
element_a.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
},
},
element_c.to_string() => {
meta: {
name: "C",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: ON.level as u64,
format!("lease_{}", lease_c_id) => "level_1@C",
format!("lease_status_{}", lease_c_id) => "Satisfied",
},
relationships: {
element_a.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1p" },
},
},
},
},
"events": contains {},
}}}});
// Drop Lease on B.
// A's required level should remain ON.
// B's required level should become OFF because the lease was dropped.
// C's required level should become OFF because the lease is now Pending.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A's required level should become OFF.
// B's required level should remain OFF.
// C's required level should remain OFF.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, OFF);
broker_status.assert_matches(&broker);
// Update A's current level to OFF.
// A, B & C's required levels should remain OFF.
broker.update_current_level(&element_a, OFF);
broker_status.assert_matches(&broker);
// Leases C & B should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
element_a.to_string() => {
meta: {
name: "A",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
element_b.to_string() => {
meta: {
name: "B",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {
element_a.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" }},
},
},
element_c.to_string() => {
meta: {
name: "C",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {
element_a.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1p" },
},
},
},
},
"events": contains {},
}}}});
}
#[fuchsia::test]
async fn test_drop_opportunistic_lease_before_assertive_claim_satisifed() {
// Tests that if a lease has an opportunistic claim that has been satisfied by
// an assertive claim, and then the lease is dropped *before* the assertive claim
// was satisfied, that the opportunistic claim should not be enforced, even though
// it would have, had the lease not been dropped prematurely.
//
// A has an assertive dependency on B.
// C has an opportunistic dependency on B.
//
// A B C
// ON => ON <- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_b_assertive = DependencyToken::create();
let token_b_opportunistic = DependencyToken::create();
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for A, B & C should be OFF.
// Set A, B & C's current level to OFF.
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for A, B & C should be OFF.
// Set A, B & C's current level to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
// Lease C.
// All required levels should be OFF, as B is not on and opportunistic.
// Lease C should be pending and contingent.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease A.
// B's required level should be ON.
// A and C's required levels should be OFF.
// Lease A should be pending and non-contingent.
// Lease C should be pending and non-contingent.
let lease_a = broker.acquire_lease(&element_a, ON).expect("acquire failed");
let lease_a_id = lease_a.id.clone();
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on A.
// All required levels should be OFF as there is no longer an assertive claim.
// Lease C should be pending and contingent.
broker.drop_lease(&lease_a.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease ON C.
// All required levels should remain OFF.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.assert_matches(&broker);
// Leases A & C should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_a_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_immediate() {
// Tests that a lease with an opportunistic claim is immediately satisfied if
// there are already leases with assertive claims that would satisfy its
// opportunistic claim.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// A B C
// ON <= ON
// ON <------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for A, B & C should be OFF.
// Set A, B & C's current level to OFF.
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for A, B & C should be OFF.
// Set A, B & C's current level to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B's required level should be OFF because A is not yet on.
// C's required level should remain OFF.
// Lease B should be pending.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON.
// C's required level should remain OFF.
// Lease B should become satisfied.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
// Lease C.
// A's required level should remain ON.
// B's required level should remain ON.
// C's required level should become ON because its dependencies are
// already satisfied.
// Lease B should still be satisfied.
// Lease C should be immediately satisfied because B's assertive claim on
// A satisfies C's opportunistic claim.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.required_level.update(&element_c, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B.
// A's required level should remain ON.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because its lease is now
// pending and contingent.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A's required level should become OFF.
// B's required level should remain OFF.
// C's required level should remain OFF.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, OFF);
broker_status.assert_matches(&broker);
// Update A's current level to OFF.
// A's required level should remain OFF.
// B's required level should remain OFF.
// C's required level should remain OFF.
broker.update_current_level(&element_a, OFF);
broker_status.assert_matches(&broker);
// Leases C & B should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_partially_satisfied() {
// Tests that a lease with two opportunistic claims, one which is satisfied
// initially by a second lease, and then the other that is satisfied
// by a third lease, correctly becomes satisfied.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A and E.
// D has an assertive dependency on E.
// A B C D E
// ON <= ON
// ON <------- ON -------> ON
// ON => ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let token_e_assertive = DependencyToken::create();
let token_e_opportunistic = DependencyToken::create();
let element_e = broker
.add_element(
"E",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_e_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_e_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_e_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_e_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for A, B, C, D & E should be OFF.
// Set A, B, C, D & E's current level to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker.update_current_level(&element_d, OFF);
broker.update_current_level(&element_e, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.required_level.update(&element_d, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B, C, D & E's required levels should remain OFF.
// Lease B should be pending.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON.
// C, D & E's required level should remain OFF.
// Lease B should become satisfied.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
// Lease C.
// A & B's required levels should remain ON.
// C's required level should remain OFF because its lease is pending and contingent.
// D & E's required level should remain OFF.
// Lease B should still be satisfied.
// Lease C should be contingent because while B's assertive claim on
// A satisfies C's opportunistic claim on A, nothing satisfies C's opportunistic
// claim on E.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease D.
// A & B's required levels should remain ON.
// C & D's required levels should remain OFF because their dependencies are not yet satisfied.
// E's required level should become ON because of D's assertive claim.
// Lease B should still be satisfied.
// Lease C should be pending, but no longer contingent.
// Lease D should be pending.
let lease_d = broker.acquire_lease(&element_d, ON).expect("acquire failed");
let lease_d_id = lease_d.id.clone();
broker_status.required_level.update(&element_e, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
// Update E's current level to ON.
// A & B's required level should remain ON.
// C & D's required levels should become ON because their dependencies are now satisfied.
// E's required level should remain ON.
// Lease B should still be satisfied.
// Lease C should become satisfied.
// Lease D should become satisfied.
broker.update_current_level(&element_e, ON);
broker_status.required_level.update(&element_c, ON);
broker_status.required_level.update(&element_d, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
// Drop Lease on B.
// A's required level should remain ON because C has not yet dropped its lease.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because it is now pending and contingent.
// D & E's required level should remain ON.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on D.
// A's required level should remain ON because C has not yet dropped its lease.
// B's required level should remain OFF.
// C's required level should remain OFF.
// D's required level should become OFF because it is no longer leased.
// E's required level should remain ON because C has not yet dropped its lease.
// Lease C should still be pending and contingent.
broker.drop_lease(&lease_d.id).expect("drop_lease failed");
broker_status.required_level.update(&element_d, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A & E's required levels should become OFF.
// B, C & D's required levels should remain OFF.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
// Update A's current level to OFF.
// All required levels should remain OFF.
broker.update_current_level(&element_a, OFF);
broker_status.assert_matches(&broker);
// Update E's current level to OFF.
// All required levels should remain OFF.
broker.update_current_level(&element_e, OFF);
broker_status.assert_matches(&broker);
// Leases C & B should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
assert_lease_cleaned_up(&broker.catalog, &lease_d_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_reacquire() {
// Tests that a lease with an opportunistic claim is dropped and reacquired
// will not prevent power-down.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// A B C
// ON <= ON
// ON <------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
// All initial required levels should be OFF.
// Set all current levels to OFF.
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
// Lease C.
// A's required level should remain OFF because of C's opportunistic claim.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
// Lease C should be pending and contingent on its opportunistic claim.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B's required level should remain OFF because its dependency is not satisfied.
// C's required level should remain OFF because its dependency is not satisfied.
// Lease B should be pending.
// Lease C should remain pending but no longer contingent because
// B's assertive claim would satisfy C's opportunistic claim.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because its dependency is satisfied.
// C's required level should become ON because its dependency is satisfied.
// Lease B & C should become satisfied.
broker.update_current_level(&element_a, ON);
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(ON));
assert_eq!(broker.get_required_level(&element_c), Some(ON));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B.
// A's required level should remain ON.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because it now pending and contingent.
// Lease C should now be Contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A's required level should become OFF.
// B's required level should remain OFF.
// C's required level should remain OFF because it is no longer leased.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// Reacquire Lease on C.
// A's required level should remain OFF despite C's new opportunistic claim.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
// The lease on C should remain Pending and contingent even though A's current level is ON.
let lease_c_reacquired = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_reacquired_id = lease_c_reacquired.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c_reacquired.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c_reacquired.id), true);
// Update A's current level to OFF.
// A, B & C's required levels should remain OFF.
broker.update_current_level(&element_a, OFF);
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// Drop reacquired lease on C.
// A, B & C's required levels should remain OFF.
broker.drop_lease(&lease_c_reacquired.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_reacquired_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_reuse() {
// Tests that a lease with an opportunistic claim can be reused after
// the current level of the consumer element is lowered.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// A B C
// ON <= ON
// ON <------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
// All initial required levels should be OFF.
// Set all current levels to OFF.
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
// Lease C.
// A's required level should remain OFF because of C's opportunistic claim.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
// Lease C should be pending and contingent on its opportunistic claim.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B's required level should remain OFF because its dependency is not satisfied.
// C's required level should remain OFF because its dependency is not satisfied.
// Lease B should be pending.
// Lease C should remain pending but no longer contingent because
// B's assertive claim would satisfy C's opportunistic claim.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because its dependency is satisfied.
// C's required level should become ON because its dependency is satisfied.
// Lease B & C should become satisfied.
broker.update_current_level(&element_a, ON);
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(ON));
assert_eq!(broker.get_required_level(&element_c), Some(ON));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update C's current level to ON.
// A's required level should remain ON.
// B's required level should remain ON.
// C's required level should remain ON.
// Lease B & C should remain satisfied.
broker.update_current_level(&element_c, ON);
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(ON));
assert_eq!(broker.get_required_level(&element_c), Some(ON));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B.
// A's required level should remain ON.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because it now pending and contingent.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Update C's current level to OFF.
// A's required level should become OFF.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
// Lease C should still be pending and contingent.
broker.update_current_level(&element_c, OFF);
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Update A's current level to OFF.
// A, B & C's required levels should remain OFF.
// Lease C should still be pending and contingent.
broker.update_current_level(&element_a, OFF);
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Update B's current level to OFF.
// A, B & C's required levels should remain OFF.
// Lease C should still be pending and contingent.
broker.update_current_level(&element_b, OFF);
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Reacquire Lease on B.
// A's required level should become ON because of B's assertive claim.
// B's required level should remain OFF because its dependency is not satisfied.
// C's required level should remain OFF because its dependency is not satisfied.
// Lease B should be pending.
// Lease C should remain pending but no longer contingent because
// B's assertive claim would satisfy C's opportunistic claim.
let lease_b_reacquired = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_reacquired_id = lease_b_reacquired.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_b_reacquired.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b_reacquired.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because its dependency is satisfied.
// C's required level should become ON because its dependency is satisfied.
// Lease B & C should become satisfied.
broker.update_current_level(&element_a, ON);
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(ON));
assert_eq!(broker.get_required_level(&element_c), Some(ON));
assert_eq!(broker.get_lease_status(&lease_b_reacquired.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b_reacquired.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop reacquired lease on B.
// A's required level should remain ON.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because it now pending and contingent.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b_reacquired.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ON));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop lease on C.
// A's required level should become OFF.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
assert_lease_cleaned_up(&broker.catalog, &lease_b_reacquired_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_contingent() {
// Tests that a lease with an opportunistic claim does not affect required
// levels if it has not yet been satisfied.
//
// B has an assertive dependency on A @ 3.
// C has an opportunistic dependency on A @ 2.
// D has an assertive dependency on A @ 1.
// A B C D
// 3 <== 1
// 2 <-------- 1
// 1 <============== 1
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
0,
vec![0, 1, 2, 3],
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
0,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![3],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
0,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 1,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
0,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for all elements should be 0.
// Set all current levels to 0.
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker_status.required_level.update(&element_d, ZERO);
broker_status.assert_matches(&broker);
broker.update_current_level(&element_a, ZERO);
broker.update_current_level(&element_b, ZERO);
broker.update_current_level(&element_c, ZERO);
broker.update_current_level(&element_d, ZERO);
// Lease C.
let lease_c = broker.acquire_lease(&element_c, ONE).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
// A's required level should remain 0 despite C's opportunistic claim.
// B's required level should remain 0.
// C's required level should remain 0 because its lease is pending and contingent.
// D's required level should remain 0.
broker_status.assert_matches(&broker);
// Lease C should be pending and contingent on its opportunistic claim.
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B.
let lease_b = broker.acquire_lease(&element_b, ONE).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
// A's required level should become 3 because of B's assertive claim.
// B's required level should remain 0 because its dependency is not yet satisfied.
// C's required level should remain 0 because its dependency is not yet satisfied.
// D's required level should remain 0.
broker_status.required_level.update(&element_a, THREE);
broker_status.assert_matches(&broker);
// Lease B should be pending.
// Lease C should remain pending but no longer contingent because
// B's assertive claim would satisfy C's opportunistic claim.
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to 3.
broker.update_current_level(&element_a, THREE);
// A's required level should remain 3.
// B's required level should become 1 because its dependency is now satisfied.
// C's required level should become 1 because its dependency is now satisfied.
// D's required level should remain 0.
broker_status.required_level.update(&element_b, ONE);
broker_status.required_level.update(&element_c, ONE);
broker_status.assert_matches(&broker);
// Lease B & C should become satisfied.
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
// A's required level should drop to 2 until C drops its opportunistic claim.
// B's required level should become 0 because it is no longer leased.
// C's required level should become 0 because it is now pending and contingent.
// D's required level should remain 0.
broker_status.required_level.update(&element_a, TWO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker_status.assert_matches(&broker);
// Lease C should now be pending and contingent.
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Update A's current level to 2.
// A's required level should remain 2.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should remain 0.
broker_status.assert_matches(&broker);
// Lease C should still be pending and contingent.
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
// A's required level should become 0.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should remain 0.
broker_status.required_level.update(&element_a, ZERO);
broker_status.assert_matches(&broker);
// Reacquire Lease on C.
// The lease on C should remain Pending even though A's current level is 2.
let lease_c_reacquired = broker.acquire_lease(&element_c, ONE).expect("acquire failed");
let lease_c_reacquired_id = lease_c_reacquired.id.clone();
// A's required level should remain 0 despite C's new opportunistic claim.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should remain 0.
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c_reacquired.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c_reacquired.id), true);
// Acquire Lease on D.
let lease_d = broker.acquire_lease(&element_d, ONE).expect("acquire failed");
let lease_d_id = lease_d.id.clone();
// A's required level should become 1, but this is not enough to
// satisfy C's opportunistic claim.
// B's required level should remain 0.
// C's required level should remain 0 even though A's current level is 2.
// D's required level should become 1 immediately because its dependency is already satisfied.
broker_status.required_level.update(&element_a, ONE);
broker_status.required_level.update(&element_d, ONE);
broker_status.assert_matches(&broker);
// The lease on D should immediately be satisfied by A's current level of 2.
// The lease on C should remain Pending even though A's current level is 2.
assert_eq!(broker.get_lease_status(&lease_c_reacquired.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_c_reacquired.id), true);
// Update A's current level to 1.
broker.update_current_level(&element_a, ONE);
// A's required level should remain 1.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should remain 1.
broker_status.assert_matches(&broker);
// The lease on C should remain Pending.
// The lease on D should still be satisfied.
assert_eq!(broker.get_lease_status(&lease_c_reacquired.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c_reacquired.id), true);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
// Drop reacquired lease on C.
broker.drop_lease(&lease_c_reacquired.id).expect("drop_lease failed");
// A's required level should remain 1.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should remain 1.
broker_status.assert_matches(&broker);
// The lease on D should still be satisfied.
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
// Drop lease on D.
broker.drop_lease(&lease_d.id).expect("drop_lease failed");
// A's required level should become 0.
// B's required level should remain 0.
// C's required level should remain 0.
// D's required level should become 0 because it is no longer leased.
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_d, ZERO);
broker_status.assert_matches(&broker);
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_reacquired_id);
assert_lease_cleaned_up(&broker.catalog, &lease_d_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_levels() {
// Tests that a lease with an opportunistic claim remains unsatisfied
// if a lease with an assertive but lower level claim on the required
// element is added.
//
// B @ 10 has an assertive dependency on A @ 1.
// B @ 20 has an assertive dependency on A @ 1.
// C @ 5 has an opportunistic dependency on A @ 2.
// A B C
// 2 <= 20
// 1 <= 10
// 2 <-------- 5
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
0,
vec![0, 1, 2],
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
0,
vec![0, 10, 20],
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 10,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 20,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
0,
vec![0, 5],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 5,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for A, B & C should be OFF.
// Set A, B & C's current level to OFF.
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker.update_current_level(&element_a, ZERO);
broker.update_current_level(&element_b, ZERO);
broker.update_current_level(&element_c, ZERO);
broker_status.assert_matches(&broker);
// Lease C @ 5.
// A's required level should remain 0 because of C's opportunistic claim.
// B's required level should remain 0.
// C's required level should remain 0 because its lease is pending and contingent.
// Lease C should be pending because it is contingent on an opportunistic claim.
let lease_c = broker
.acquire_lease(&element_c, IndexedPowerLevel { level: 5, index: 1 })
.expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B @ 10.
// A's required level should become 1 because of B's assertive claim.
// B's required level should remain 0 because its dependency is not yet satisfied.
// C's required level should remain 0 because its lease is pending and contingent.
// Lease B @ 10 should be pending.
// Lease C should remain pending because A @ 1 does not satisfy its claim.
let lease_b_10 = broker
.acquire_lease(&element_b, IndexedPowerLevel { level: 10, index: 1 })
.expect("acquire failed");
let lease_b_10_id = lease_b_10.id.clone();
broker_status.required_level.update(&element_a, ONE);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Update A's current level to 1.
// A's required level should remain 1.
// B's required level should become 10 because its dependency is now satisfied.
// C's required level should remain 0 because A @ 1 does not satisfy its claim.
// Lease B @ 10 should become satisfied.
// Lease C should remain pending and contingent because A @ 1 does not
// satisfy its claim.
broker.update_current_level(&element_a, ONE);
broker_status.required_level.update(&element_b, IndexedPowerLevel { level: 10, index: 1 });
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B @ 20.
// A's required level should become 2 because of the new lease's assertive claim.
// B's required level should remain 10 because the new lease's claim is not yet satisfied.
// C's required level should remain 0 because its dependency is not yet satisfied.
// Lease B @ 10 should still be satisfied.
// Lease B @ 20 should be pending.
// Lease C should remain pending because A is not yet at 2, but
// should no longer be contingent on its opportunistic claim.
let lease_b_20 = broker
.acquire_lease(&element_b, IndexedPowerLevel { level: 20, index: 2 })
.expect("acquire failed");
let lease_b_20_id = lease_b_20.id.clone();
broker_status.required_level.update(&element_a, TWO);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_b_20.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to 2.
// A's required level should remain 2.
// B's required level should become 20 because the new lease's claim is now satisfied.
// C's required level should become 5 because its dependency is now satisfied.
// Lease B @ 10 should still be satisfied.
// Lease B @ 20 should become satisfied.
// Lease C should become satisfied because A @ 2 satisfies its opportunistic claim.
broker.update_current_level(&element_a, TWO);
broker_status.required_level.update(&element_b, IndexedPowerLevel { level: 20, index: 2 });
broker_status.required_level.update(&element_c, IndexedPowerLevel { level: 5, index: 1 });
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_b_20.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B @ 20.
// A's required level should remain 2 because of C's opportunistic claim.
// B's required level should become 10 because the higher lease has been dropped.
// C's required level should become 0 because its lease is now pending and contingent.
// Lease B @ 10 should still be satisfied.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b_20.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, IndexedPowerLevel { level: 10, index: 1 });
broker_status.required_level.update(&element_c, ZERO);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A's required level should become 1 because C has dropped its claim,
// but B's lower lease is still being held.
// B's required level should remain 10.
// C's required level should remain 0.
// Lease B @ 10 should still be satisfied.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, ONE);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
// Update A's current level to 1.
// A's required level should remain 1.
// B's required level should remain 10.
// C's required level should remain 0.
// Lease B @ 10 should still be satisfied.
broker.update_current_level(&element_a, ONE);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b_10.id), Some(LeaseStatus::Satisfied));
// Drop Lease on B @ 10.
// A's required level should drop to 0 because all claims have been dropped.
// B's required level should become 0 because its leases have been dropped.
// C's required level should remain 0.
broker.drop_lease(&lease_b_10.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker_status.assert_matches(&broker);
// Update A's current level to 0.
// A's required level should remain 0.
// B's required level should remain 0.
// C's required level should remain 0.
broker.update_current_level(&element_a, ZERO);
broker_status.assert_matches(&broker);
// Leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_10_id);
assert_lease_cleaned_up(&broker.catalog, &lease_b_20_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_independent_assertive() {
// Tests that independent assertive claims of a lease are not activated
// while a lease is Contingent (has one or more opportunistic claims
// but no other leases have assertive claims that would satisfy them).
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// C has an assertive dependency on D.
// A B C D
// ON <= ON
// ON <------- ON => ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let token_d_assertive = DependencyToken::create();
let element_d = broker
.add_element(
"D",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_d_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_d_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level for all elements should be OFF.
// Set all current level to OFF.
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.required_level.update(&element_d, OFF);
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker.update_current_level(&element_d, OFF);
broker_status.assert_matches(&broker);
// Lease C.
// A's required level should remain OFF because C's opportunistic claim
// does not raise the required level of A.
// B's required level should remain OFF.
// C's required level should remain OFF because its lease is pending and contingent.
// D's required level should remain OFF C's opportunistic claim on A has
// no other assertive claims that would satisfy it.
// Lease C should be pending and contingent because its opportunistic
// claim has no other assertive claim that would satisfy it.
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
let lease_c_id = lease_c.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Lease B.
// A's required level should become ON because of B's assertive claim.
// B's required level should remain OFF because its dependency is not yet satisfied.
// C's required level should remain OFF because its dependencies are not yet satisfied.
// D's required level should become ON because C's opportunistic claim would
// be satisfied by B's assertive claim.
// Lease B should be pending.
// Lease C should be pending but no longer contingent because B's
// assertive claim would satisfy C's opportunistic claim.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.required_level.update(&element_d, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because its dependency is now satisfied.
// C's required level should remain OFF because its dependencies are not yet satisfied.
// D's required level should remain ON.
// Lease B should become satisfied.
// Lease C should still be pending.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Update D's current level to ON.
// A's required level should remain ON.
// B's required level should remain ON.
// C's required level should become ON because its dependencies are now satisfied.
// D's required level should remain ON.
// Lease B should still be satisfied.
// Lease C should become satisfied.
broker.update_current_level(&element_d, ON);
broker_status.required_level.update(&element_c, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_c.id), false);
// Drop Lease on B.
// A's required level should remain ON.
// B's required level should become OFF because it is no longer leased.
// C's required level should become OFF because its lease is now pending and contingent.
// D's required level should remain ON until C has dropped the lease.
// Lease C should now be pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
// Drop Lease on C.
// A's required level should become OFF because C has dropped its opportunistic claim.
// B's required level should remain OFF.
// C's required level should remain OFF.
// D's required level should become OFF because C has dropped its assertive claim.
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_d, OFF);
broker_status.assert_matches(&broker);
// Update A's current level to OFF.
// All required levels should remain OFF.
broker.update_current_level(&element_a, OFF);
broker_status.assert_matches(&broker);
// Update D's current level to OFF.
// All required levels should remain OFF.
broker.update_current_level(&element_d, OFF);
broker_status.assert_matches(&broker);
// Leases C & B should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_c_id);
}
#[fuchsia::test]
async fn test_assertive_claims_do_not_satisfy_opportunistic_claims_while_contingent() {
// Test that assertive claims from a contingent lease do not satisfy opportunistic claims until that
// lease is made non-contingent.
//
// B has an assertive dependency on C.
// D has an assertive dependency on A and an opportunistic dependency on C.
// E has an opportunistic dependency on A.
// A B C D E
// ON => ON
// ON <============= ON
// ON <- ON
// ON <------------------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let token_c_assertive = DependencyToken::create();
let token_c_opportunistic = DependencyToken::create();
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let token_b_assertive = DependencyToken::create();
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let element_e = broker
.add_element(
"E",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required levels for all elements should be OFF.
// Set all current levels to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker.update_current_level(&element_d, OFF);
broker.update_current_level(&element_e, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.required_level.update(&element_d, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
// Lease E.
// A's required level should remain OFF because of E's opportunistic claim.
// B, C, D & E's required level are unaffected and should remain OFF.
// Lease E should be pending and contingent on its opportunistic claim.
let lease_e = broker.acquire_lease(&element_e, ON).expect("acquire failed");
let lease_e_id = lease_e.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Lease D.
// A's required level should remain OFF. While Lease D has an assertive claim on A, it is not
// activated until it is not contingent on C.
// C's required level should remain OFF because of D's opportunistic claim.
// B, D & E's required level are unaffected and should remain OFF.
// Lease D should be pending and contingent on its opportunistic claim.
// Lease E should be pending and contingent on its opportunistic claim, which is not satisfied
// by Lease D's assertive claim, as Lease D is contingent.
let lease_d = broker.acquire_lease(&element_d, ON).expect("acquire failed");
let lease_d_id = lease_d.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_d.id), true);
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Lease B.
// A should have required level ON, because D is no longer contingent on C.
// C should have required level ON, because of B's assertive claim.
// B, D & E's required level are unaffected and should remain OFF.
// Lease B should be pending and not contingent.
// Lease D should be pending and not contingent.
// Lease E should be pending and not contingent.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_b_id = lease_b.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.required_level.update(&element_c, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Update C's current level to ON.
// A should still have required level ON.
// B should have required level ON, now that it's lease is satisfied.
// C should still have required level ON.
// D & E's required level are unaffected and should remain OFF.
// Lease B should now be satisfied and not contingent.
// Lease D and E should be pending as A is not yet ON, and not contingent.
broker.update_current_level(&element_c, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Update A's current level to ON.
// A should still have required level ON.
// B should still have required level ON.
// C should still have required level ON.
// D & E's required level should be ON, now that their leases are satisfied.
// Lease B, D and E should now all be satisfied.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_d, ON);
broker_status.required_level.update(&element_e, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_b.id), false);
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Drop lease for B.
// A should still have required level ON.
// C should still have required level ON.
// B, D & E's required levels are now OFF as their leases are no longer satisfied.
// Lease D should drop to pending and contingent.
// Lease E should drop to pending and contingent.
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_d, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_d.id), true);
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Drop lease for D.
// A should still have required level ON.
// C should have required level OFF (no leases require C anymore).
// B, D & E's required level are unaffected and should remain OFF.
// Lease E should remain at pending and contingent.
broker.drop_lease(&lease_d.id).expect("drop_lease failed");
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Drop lease for E.
// A should have required level OFF.
// C should still have required level OFF.
// B, D & E's required level are unaffected and should remain OFF.
broker.drop_lease(&lease_e.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, OFF);
broker_status.assert_matches(&broker);
// Leases B, D and E should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b_id);
assert_lease_cleaned_up(&broker.catalog, &lease_d_id);
assert_lease_cleaned_up(&broker.catalog, &lease_e_id);
}
#[fuchsia::test]
async fn test_lease_opportunistic_chained() {
// Tests that assertive dependencies, which depend on opportunistic dependencies,
// which in turn depend on assertive dependencies, all work as expected.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on B (and transitively, an opportunistic dependency on A).
// D has an assertive dependency on B (and transitively, an assertive dependency on A).
// E has an assertive dependency on C (and transitively, an opportunistic dependency on A & B).
// A B C D E
// ON <= ON
// ON <- ON <======= ON
// ON <======= ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("dup failed").into()],
vec![],
)
.expect("add_element failed");
let token_b_assertive = DependencyToken::create();
let token_b_opportunistic = DependencyToken::create();
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let token_c_assertive = DependencyToken::create();
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_e = broker
.add_element(
"E",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required levels for all elements should be OFF.
// Set all current levels to OFF.
broker.update_current_level(&element_a, OFF);
broker.update_current_level(&element_b, OFF);
broker.update_current_level(&element_c, OFF);
broker.update_current_level(&element_d, OFF);
broker.update_current_level(&element_e, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.required_level.update(&element_c, OFF);
broker_status.required_level.update(&element_d, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
// Lease E.
// A, B, & C's required levels should remain OFF because of C's opportunistic claim.
// D's required level should remain OFF.
// E's required level should remain OFF because its lease is pending and contingent.
// Lease E should be pending and contingent on its opportunistic claim.
let lease_e = broker.acquire_lease(&element_e, ON).expect("acquire failed");
let lease_e_id = lease_e.id.clone();
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Lease D.
// A's required level should become ON because of D's transitive assertive claim.
// B's required level should remain OFF because A is not yet ON.
// C's required level should remain OFF because B is not yet ON.
// D's required level should remain OFF because B is not yet ON.
// E's required level should remain OFF because C is not yet ON.
// Lease D should be pending.
// Lease E should remain pending but is no longer contingent.
let lease_d = broker.acquire_lease(&element_d, ON).expect("acquire failed");
let lease_d_id = lease_d.id.clone();
broker_status.required_level.update(&element_a, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Update A's current level to ON.
// A's required level should remain ON.
// B's required level should become ON because of D's assertive claim and
// its dependency on A being satisfied.
// C's required level should remain OFF because B is not ON.
// D's required level should remain OFF because B is not yet ON.
// E's required level should remain OFF because C is not yet ON.
// Lease D & E should remain pending.
broker.update_current_level(&element_a, ON);
broker_status.required_level.update(&element_b, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Update B's current level to ON.
// A & B's required level should remain ON.
// C's required level should become ON because B is now ON.
// D's required level should become ON because B is now ON.
// E's required level should remain OFF because C is not yet ON.
// Lease D should become satisfied.
broker.update_current_level(&element_b, ON);
broker_status.required_level.update(&element_c, ON);
broker_status.required_level.update(&element_d, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Update C's current level to ON.
// A, B, C & D's required level should remain ON.
// E's required level should become ON because C is now ON.
// Lease E should become satisfied.
broker.update_current_level(&element_c, ON);
broker_status.required_level.update(&element_e, ON);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_d.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_d.id), false);
assert_eq!(broker.is_lease_contingent(&lease_e.id), false);
// Drop Lease on D.
// A, B & C's required levels should remain ON.
// D's required level should become OFF because it is no longer leased.
// E's required level should become OFF because its lease is now pending and contingent.
// Lease E should become pending and contingent.
broker.drop_lease(&lease_d.id).expect("drop_lease failed");
broker_status.required_level.update(&element_d, OFF);
broker_status.required_level.update(&element_e, OFF);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_e.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_e.id), true);
// Drop Lease on E.
// A's required level should remain ON because B is still ON.
// B's required level should remain ON because C is still ON.
// C's required level should become OFF because E has dropped its claim.
// D's required level should remain OFF.
// E's required level should remain OFF.
broker.drop_lease(&lease_e.id).expect("drop_lease failed");
broker_status.required_level.update(&element_c, OFF);
broker_status.assert_matches(&broker);
// Update C's current level to OFF.
// A's required level should remain ON because B is still ON.
// B's required level should become OFF because C is now OFF.
// C's required level should remain OFF.
// D's required level should remain OFF.
// E's required level should remain OFF.
broker.update_current_level(&element_c, OFF);
broker_status.required_level.update(&element_b, OFF);
broker_status.assert_matches(&broker);
// Update B's current level to OFF.
// A's required level should become OFF because B is now OFF.
// B, C, D & E's required levels should remain OFF.
broker.update_current_level(&element_b, OFF);
broker_status.required_level.update(&element_a, OFF);
broker_status.assert_matches(&broker);
// Update A's current level to OFF.
// All required levels should remain OFF.
broker.update_current_level(&element_b, OFF);
broker_status.assert_matches(&broker);
// Leases D and E should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_d_id);
assert_lease_cleaned_up(&broker.catalog, &lease_e_id);
}
#[fuchsia::test]
async fn test_lease_cumulative_implicit_dependency() {
// Tests that cumulative implicit dependencies are properly resolved when a lease is
// acquired. Verifies a simple case of assertive dependencies only.
//
// A[1] has an assertive dependency on B[1].
// A[2] has an assertive dependency on C[1].
//
// A[2] has an implicit, assertive dependency on B[1].
//
// A B C
// 1 ==> 1
// 2 ========> 1
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let v012_u8: Vec<u8> = vec![0, 1, 2];
let token_b_assertive = DependencyToken::create();
let token_c_assertive = DependencyToken::create();
let element_b = broker
.add_element(
"B",
0,
v012_u8.clone(),
vec![],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
0,
v012_u8.clone(),
vec![],
vec![token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_a = broker
.add_element(
"A",
0,
v012_u8.clone(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 2,
requires_token: token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
],
vec![],
vec![],
)
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required levels for all elements should be 0.
// Set all current levels to 0.
broker.update_current_level(&element_a, ZERO);
broker.update_current_level(&element_b, ZERO);
broker.update_current_level(&element_c, ZERO);
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker_status.assert_matches(&broker);
// Lease A[2].
//
// A has two assertive dependencies, B[1] and C[1].
//
// A's required level should not change.
// B and C's required level should be 1.
//
// A's lease is pending and not contingent.
let lease_a = broker.acquire_lease(&element_a, TWO).expect("acquire failed");
let lease_a_id = lease_a.id.clone();
broker_status.required_level.update(&element_b, ONE);
broker_status.required_level.update(&element_c, ONE);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
// Update B, C's current level to 1.
//
// A's current level should now be 2.
// B and C's current level should not change.
//
// A's lease should be satisfied and not contingent.
broker.update_current_level(&element_b, ONE);
broker.update_current_level(&element_c, ONE);
broker_status.required_level.update(&element_a, TWO);
broker_status.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
// Drop Lease A.
//
// A, B and C's required level should be 0.
//
// Lease A should be pending and not contingent.
broker.drop_lease(&lease_a.id).expect("drop_lease failed");
broker_status.required_level.update(&element_a, ZERO);
broker_status.required_level.update(&element_b, ZERO);
broker_status.required_level.update(&element_c, ZERO);
broker_status.assert_matches(&broker);
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_a_id);
}
#[fuchsia::test]
async fn test_lease_cumulative_implicit_transitive_dependency() {
// Tests that cumulative implicit transitive dependencies (both opportunistic
// and assertive) are properly requested when a lease is acquired.
//
// A[1] has an assertive dependency on D[1].
// A[2] has an opportunistic dependency on C[1].
// A[3] has an assertive dependency on B[2].
// D[1] has an assertive dependency on E[1].
// D[1] has an opportunistic dependency on B[1].
// F[1] has an assertive dependency on C[1].
//
// A[3] has an implicit, transitive, opportunistic dependency on B[1].
// A[3] has an implicit, opportunistic dependency on C[1].
// A[3] has an implicit, assertive dependency on D[1].
// A[3] has an implicit, transitive, assertive dependency on E[1].
//
// A B C D E F
// 1 ==============> 1 ==> 1
// 1 <-------- 1
// 2 --------> 1 <============== 1
// 3 ==> 2
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let v0123_u8: Vec<u8> = vec![0, 1, 2, 3];
let token_b_assertive = DependencyToken::create();
let token_b_opportunistic = DependencyToken::create();
let token_c_assertive = DependencyToken::create();
let token_c_opportunistic = DependencyToken::create();
let token_d_assertive = DependencyToken::create();
let token_e_assertive = DependencyToken::create();
let element_b = broker
.add_element(
"B",
0,
v0123_u8.clone(),
vec![],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_e = broker
.add_element(
"E",
0,
v0123_u8.clone(),
vec![],
vec![token_e_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_d = broker
.add_element(
"D",
0,
v0123_u8.clone(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_e_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 1,
requires_token: token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
],
vec![token_d_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
0,
v0123_u8.clone(),
vec![],
vec![token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_f = broker
.add_element(
"F",
0,
v0123_u8.clone(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_a = broker
.add_element(
"A",
0,
v0123_u8.clone(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 1,
requires_token: token_d_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 2,
requires_token: token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 3,
requires_token: token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
},
],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for all elements should be OFF.
// Set all current levels to OFF.
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
assert_eq!(broker.get_required_level(&element_d), Some(ZERO));
assert_eq!(broker.get_required_level(&element_e), Some(ZERO));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
broker.update_current_level(&element_a, ZERO);
broker.update_current_level(&element_b, ZERO);
broker.update_current_level(&element_c, ZERO);
broker.update_current_level(&element_d, ZERO);
broker.update_current_level(&element_e, ZERO);
broker.update_current_level(&element_f, ZERO);
// Lease A[3].
//
// A has two opportunistic dependencies, on B[1] and D[1].
// A, B, C, D, E and F's required levels should not change.
//
// A's lease is pending and contingent.
let lease_a = broker.acquire_lease(&element_a, THREE).expect("acquire failed");
let lease_a_id = lease_a.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
assert_eq!(broker.get_required_level(&element_d), Some(ZERO));
assert_eq!(broker.get_required_level(&element_e), Some(ZERO));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), true);
// Lease F[1].
//
// F has an assertive claim on C[1].
// A has an assertive claim on B[2] that should now satisfy D[1]->B[1].
// B's required level should now be 2.
// C's required level should now be 1.
// E's required level should now be 1.
// A, D and F's required level should not change.
//
// A's lease is pending and not contingent.
// F's lease is pending and not contingent.
let lease_f = broker.acquire_lease(&element_f, ONE).expect("acquire failed");
let lease_f_id = lease_f.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
assert_eq!(broker.get_required_level(&element_d), Some(ZERO));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_f.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
assert_eq!(broker.is_lease_contingent(&lease_f.id), false);
// Update B, C and E's current level to 1.
//
// D's required level should now be 1.
// A, B, C, E, and F's required level should not change.
//
// A's lease should be pending and not contingent.
// F's lease should be satisfied and not contingent.
broker.update_current_level(&element_b, ONE);
broker.update_current_level(&element_c, ONE);
broker.update_current_level(&element_e, ONE);
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
assert_eq!(broker.get_required_level(&element_d), Some(ONE));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ONE));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_f.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
assert_eq!(broker.is_lease_contingent(&lease_f.id), false);
// Update B's current level to 2 and D's current level to 1.
//
// A's required level should now be 3.
// D's required level should now be 1.
// B, C, E, and F's required level should not change.
//
// A's lease should be satisfied and not contingent.
// F's lease should be satisfied and not contingent.
broker.update_current_level(&element_b, TWO);
broker.update_current_level(&element_d, ONE);
assert_eq!(broker.get_required_level(&element_a), Some(THREE));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
assert_eq!(broker.get_required_level(&element_d), Some(ONE));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ONE));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_f.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
assert_eq!(broker.is_lease_contingent(&lease_f.id), false);
// Update A's current level to 3 and F's current level to 1.
//
// No required levels should change.
//
// Both leases should be satisfied and not contingent.
broker.update_current_level(&element_a, THREE);
broker.update_current_level(&element_f, ONE);
assert_eq!(broker.get_required_level(&element_a), Some(THREE));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
assert_eq!(broker.get_required_level(&element_d), Some(ONE));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ONE));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.get_lease_status(&lease_f.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
assert_eq!(broker.is_lease_contingent(&lease_f.id), false);
// Drop Lease F.
//
// B's required level should stay at 2.
// C's required level should stay at 1.
// D's required level should stay at 1.
// E's required level should stay at 1.
//
// Lease A should be pending and contingent.
broker.drop_lease(&lease_f.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
assert_eq!(broker.get_required_level(&element_d), Some(ONE));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), true);
// Drop Lease A.
//
// B's required level should stay at 1 (still used by D[1]).
// E's required level should stay at 1 (still used by D[1]).
//
// All required levels should drop to 0.
broker.drop_lease(&lease_a.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ONE));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
assert_eq!(broker.get_required_level(&element_d), Some(ZERO));
assert_eq!(broker.get_required_level(&element_e), Some(ONE));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
// Update D's current level to 0.
//
// B's required level should drop to 0.
broker.update_current_level(&element_d, ZERO);
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
assert_eq!(broker.get_required_level(&element_d), Some(ZERO));
assert_eq!(broker.get_required_level(&element_e), Some(ZERO));
assert_eq!(broker.get_required_level(&element_f), Some(ZERO));
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_a_id);
assert_lease_cleaned_up(&broker.catalog, &lease_f_id);
}
#[fuchsia::test]
async fn test_lease_implicit_dependency_self_satisfying() {
// Tests that cumulative implicit dependencies (both opportunistic and assertive) are properly
// requested when a lease is acquired and when the requested element can satisfy it's
// opportunistic dependency with it's own assertive dependency.
//
// A[1] has an opportunistic dependency on B[1].
// A[2] has an assertive dependency on B[2].
// A[3] has an assertive dependency on C[2].
// B[2] has an opportunistic dependency on C[1].
//
// A[3] has an implicit, opportunistic dependency on B[1].
// A[3] has an implicit, assertive dependency on B[2].
// A[2] has an implicit, transitive, opportunistic dependency on C[1].
//
// As A[3]->C[2] satisfies the opportunistic dependency B[2]->C[1], and A[2]->B[2] satisfies the
// opportunistic dependency A[1]->B[1], this lease would be self-satisfying.
//
// A B C
// 1 --> 1
// 2 ==> 2 --> 1
// 3 ========> 2
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let v0123_u8: Vec<u8> = vec![0, 1, 2, 3];
let token_b_assertive = DependencyToken::create();
let token_b_opportunistic = DependencyToken::create();
let token_c_assertive = DependencyToken::create();
let token_c_opportunistic = DependencyToken::create();
let element_c = broker
.add_element(
"C",
0,
v0123_u8.clone(),
vec![],
vec![token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
0,
v0123_u8.clone(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 2,
requires_token: token_c_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
}],
vec![token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_a = broker
.add_element(
"A",
0,
v0123_u8.clone(),
vec![
fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: 1,
requires_token: token_b_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![1],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 2,
requires_token: token_b_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
},
fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: 3,
requires_token: token_c_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![2],
},
],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for all elements should be OFF.
// Set all current levels to OFF.
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
broker.update_current_level(&element_a, ZERO);
broker.update_current_level(&element_b, ZERO);
broker.update_current_level(&element_c, ZERO);
// Lease A[3].
//
// C's required level should now be 2.
// A and B's required level should not change.
//
// A's lease should be pending and not contingent.
let lease_a = broker.acquire_lease(&element_a, THREE).expect("acquire failed");
let lease_a_id = lease_a.id.clone();
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(TWO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
// Update C's current level to 2.
//
// B's required level should now be 2.
// A and C's required level should not change.
//
// A's lease should be pending and not contingent.
broker.update_current_level(&element_c, TWO);
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(TWO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
// Update B's current level to 2.
//
// A's required level should now be 3.
// B and C's required level should not change.
//
// A's lease should be satisfied and not contingent.
broker.update_current_level(&element_b, TWO);
assert_eq!(broker.get_required_level(&element_a), Some(THREE));
assert_eq!(broker.get_required_level(&element_b), Some(TWO));
assert_eq!(broker.get_required_level(&element_c), Some(TWO));
assert_eq!(broker.get_lease_status(&lease_a.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_a.id), false);
// Drop Lease A[3].
//
// C's required level should stay at 1 (required by B[2]).
broker.drop_lease(&lease_a.id).expect("drop_lease failed");
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ONE));
// Update B's current level to 0.
//
// A and B's required level should not change.
// C's required level should now be 0.
broker.update_current_level(&element_b, ZERO);
assert_eq!(broker.get_required_level(&element_a), Some(ZERO));
assert_eq!(broker.get_required_level(&element_b), Some(ZERO));
assert_eq!(broker.get_required_level(&element_c), Some(ZERO));
// All leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_a_id);
}
#[fuchsia::test]
fn test_lease_noncontingent_to_contingent() {
// Tests that when a supportive lease is dropped, a lease that
// was noncontingent is correctly identified and made contingent.
// Found in https://fxbug.dev/342205990 as an interaction between
// Storage's Hardware (HW) and Wake-on-request (WOR) elements, and
// System Activity Governor's Wake Handling (WH) and Execution State
// elements.
// HW has a passive dep on Execution State: Wake Handling (1)
// WOR has an active dep on Wake Handling: Active, which has an active dep on ES: WH
// A persistent lease is held on HW that should be activated whenever ES is at WH or higher.
// HW -> ES(WH)
// WOR => WH(A)
// WH(A) => ES(WH)
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_sag_es_active = DependencyToken::create();
let token_sag_es_passive = DependencyToken::create();
let sag_es = broker
.add_element(
"SAG Execution State",
OFF.level,
vec![0, 1, 2],
vec![],
vec![token_sag_es_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_sag_es_passive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let token_sag_wh_active = DependencyToken::create();
let sag_wh = broker
.add_element(
"SAG Wake Handling",
OFF.level,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_sag_es_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![token_sag_wh_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let storage_hw = broker
.add_element(
"Storage Hardware",
OFF.level,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_sag_es_passive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let storage_wor = broker
.add_element(
"Storage Wake on Request",
OFF.level,
vec![0, 1],
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_sag_wh_active
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let mut required_levels = RequiredLevelMatcher::new();
// Initial required level for all elements should be OFF.
// Set all current levels to OFF.
required_levels.update(&sag_es, OFF);
required_levels.update(&sag_wh, OFF);
required_levels.update(&storage_hw, OFF);
required_levels.update(&storage_wor, OFF);
required_levels.assert_matches(&broker);
broker.update_current_level(&sag_es, OFF);
broker.update_current_level(&sag_wh, OFF);
broker.update_current_level(&storage_hw, OFF);
broker.update_current_level(&storage_wor, OFF);
// Create Persistent Lease for HW.
let lease_hw = broker.acquire_lease(&storage_hw, ON).expect("acquire failed");
let lease_hw_id = lease_hw.id.clone();
// Required levels should not have changed.
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
// Lease is contingent on the opportunistic dependency on ES(WH).
assert_eq!(broker.is_lease_contingent(&lease_hw.id), true);
// Create Lease for WOR.
let lease_wor1 = broker.acquire_lease(&storage_wor, ON).expect("acquire failed");
let lease_wor1_id = lease_wor1.id.clone();
required_levels.update(&sag_es, ON);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
// Lease is no longer contingent because the opportunistic dependency
// on ES(WH) is supported by WH's assertive dependency.
assert_eq!(broker.is_lease_contingent(&lease_hw.id), false);
assert_eq!(broker.get_lease_status(&lease_wor1.id), Some(LeaseStatus::Pending));
// Update Execution State to ON
// Persistent Lease for HW should become satisfied.
broker.update_current_level(&sag_es, ON);
required_levels.update(&sag_wh, ON);
required_levels.update(&storage_hw, ON);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_hw.id), false);
assert_eq!(broker.get_lease_status(&lease_wor1.id), Some(LeaseStatus::Pending));
// Update SAG: Wake Handling to ON
// Lease WOR should become satisfied.
broker.update_current_level(&sag_wh, ON);
required_levels.update(&storage_wor, ON);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_wor1.id), Some(LeaseStatus::Satisfied));
// Drop Lease on WOR.
broker.drop_lease(&lease_wor1.id).expect("drop_lease failed");
required_levels.update(&storage_wor, OFF);
required_levels.update(&sag_wh, OFF);
// TODO(b/346331940): Storage HW should have RL OFF here
// required_levels.update(&storage_hw, OFF);
required_levels.assert_matches(&broker);
// TODO(b/346331940): Lease on HW should become Pending and Contingent here
// assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
// assert_eq!(broker.is_lease_contingent(&lease_hw.id), true);
// Power down Storage WOR.
broker.update_current_level(&storage_wor, OFF);
required_levels.assert_matches(&broker);
broker.update_current_level(&sag_wh, OFF);
// TODO(b/346331940): Storage HW should have become OFF above.
required_levels.update(&storage_hw, OFF);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_hw.id), true);
// Create new Lease for WOR.
// Persistent lease for Storage should immediately become satisfied
// since Execution State was already on.
let lease_wor2 = broker.acquire_lease(&storage_wor, ON).expect("acquire failed");
let lease_wor2_id = lease_wor2.id.clone();
required_levels.update(&sag_es, ON);
required_levels.update(&sag_wh, ON);
required_levels.update(&storage_hw, ON);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_hw.id), false);
assert_eq!(broker.get_lease_status(&lease_wor2.id), Some(LeaseStatus::Pending));
// Power up SAG: Wake Handling.
broker.update_current_level(&sag_wh, ON);
required_levels.update(&storage_wor, ON);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Satisfied));
assert_eq!(broker.is_lease_contingent(&lease_hw.id), false);
assert_eq!(broker.get_lease_status(&lease_wor2.id), Some(LeaseStatus::Satisfied));
// Drop second Lease on WOR.
broker.drop_lease(&lease_wor2.id).expect("drop_lease failed");
required_levels.update(&storage_wor, OFF);
required_levels.update(&sag_wh, OFF);
// TODO(b/346331940): Storage HW should have RL OFF here
// required_levels.update(&storage_hw, OFF);
required_levels.assert_matches(&broker);
// TODO(b/346331940): Lease on HW should become Pending and Contingent here
// assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
// assert_eq!(broker.is_lease_contingent(&lease_hw.id), true);
// Power down Storage WOR.
broker.update_current_level(&storage_wor, OFF);
required_levels.assert_matches(&broker);
broker.update_current_level(&sag_wh, OFF);
// TODO(b/346331940): Storage HW should have become OFF above.
required_levels.update(&storage_hw, OFF);
required_levels.assert_matches(&broker);
assert_eq!(broker.get_lease_status(&lease_hw.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_hw.id), true);
// Drop lease on Storage HW
broker.drop_lease(&lease_hw.id).expect("drop_lease failed");
// TODO(b/346331940): SAG ES's RL should have become OFF above.
required_levels.update(&sag_es, OFF);
required_levels.assert_matches(&broker);
// Power down Execution State.
broker.update_current_level(&sag_es, OFF);
required_levels.assert_matches(&broker);
// Leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_hw_id);
assert_lease_cleaned_up(&broker.catalog, &lease_wor1_id);
assert_lease_cleaned_up(&broker.catalog, &lease_wor2_id);
}
#[fuchsia::test]
fn test_removing_element_permanently_prevents_lease_satisfaction() {
// Tests that if element A depends on element B, and element B is removed, that new leases
// on element A will never be satisfied.
//
// B has an assertive dependency on A.
// C has an opportunistic dependency on A.
// A B C
// ON <= ON
// <------- ON
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_a_assertive = DependencyToken::create();
let token_a_opportunistic = DependencyToken::create();
let element_a = broker
.add_element(
"A",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
)
.expect("add_element failed");
let element_b = broker
.add_element(
"B",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_a_assertive
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
let element_c = broker
.add_element(
"C",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Opportunistic,
dependent_level: ON.level,
requires_token: token_a_opportunistic
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into(),
requires_level_by_preference: vec![ON.level],
}],
vec![],
vec![],
)
.expect("add_element failed");
// Initial required level for all elements should be OFF.
// Set all current levels to OFF.
assert_eq!(broker.get_required_level(&element_a), Some(OFF));
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// Remove A.
// B & C's required level should remain OFF.
broker.remove_element(&element_a);
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
// Lease B & C.
// B & C's required level should remain OFF.
// Both leases should not be satisfied, but B and C should now be contingent, as the should
// have a new opportunistic dependency on the topology unsatisfiable element.
let lease_b = broker.acquire_lease(&element_b, ON).expect("acquire failed");
let lease_c = broker.acquire_lease(&element_c, ON).expect("acquire failed");
broker.update_current_level(&element_a, ON);
assert_eq!(broker.get_required_level(&element_b), Some(OFF));
assert_eq!(broker.get_required_level(&element_c), Some(OFF));
assert_eq!(broker.get_lease_status(&lease_b.id), Some(LeaseStatus::Pending));
assert_eq!(broker.get_lease_status(&lease_c.id), Some(LeaseStatus::Pending));
assert_eq!(broker.is_lease_contingent(&lease_b.id), true);
assert_eq!(broker.is_lease_contingent(&lease_c.id), true);
broker.drop_lease(&lease_b.id).expect("drop_lease failed");
broker.drop_lease(&lease_c.id).expect("drop_lease failed");
// Leases should be cleaned up.
assert_lease_cleaned_up(&broker.catalog, &lease_b.id);
assert_lease_cleaned_up(&broker.catalog, &lease_c.id);
}
#[fuchsia::test]
fn test_required_level() {
// Create a topology of one element.
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let element = broker
.add_element("E", 0, vec![0, 1, 2], vec![], vec![], vec![])
.expect("add_element failed");
let mut broker_status = BrokerStatusMatcher::new();
// Initial required level should be 0.
broker_status.required_level.update(&element, ZERO);
broker_status.assert_matches(&broker);
// Acquire lease for level 1.
let lease = broker.acquire_lease(&element, ONE).expect("acquire failed");
// Required level should become 1.
broker_status.required_level.update(&element, ONE);
broker_status.assert_matches(&broker);
// Drop lease.
broker.drop_lease(&lease.id).expect("drop failed");
// Required level should become 0.
broker_status.required_level.update(&element, ZERO);
broker_status.assert_matches(&broker);
// Acquire and drop a level 2 lease.
let lease = broker.acquire_lease(&element, TWO).expect("acquire failed");
broker.drop_lease(&lease.id).expect("drop failed");
// Required level should still be 0. No 2 is seen.
broker_status.assert_matches(&broker);
// Acquire lease for level 1 and check required level.
let lease = broker.acquire_lease(&element, ONE).expect("acquire failed");
broker_status.required_level.update(&element, ONE);
broker_status.assert_matches(&broker);
// Drop lease and check required level.
broker.drop_lease(&lease.id).expect("drop failed");
broker_status.required_level.update(&element, ZERO);
broker_status.assert_matches(&broker);
}
#[fuchsia::test]
fn test_add_element_dependency_list_of_levels() {
let inspect = fuchsia_inspect::component::inspector();
let inspect_node = inspect.root().create_child("test");
let mut broker = Broker::new(inspect_node);
let token_mithril = DependencyToken::create();
let mithril = broker
.add_element(
"Mithril",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![],
vec![token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed")
.into()],
vec![],
)
.expect("add_element failed");
let v01: Vec<u64> = BINARY_POWER_LEVELS.iter().map(|&v| v as u64).collect();
// Add an element with a dependency with a list of requires_level_by_preference.
// The dependency should be taken on ON, because the other levels do not
// exist.
let silver = broker
.add_element(
"Silver",
OFF.level,
BINARY_POWER_LEVELS.to_vec(),
vec![fpb::LevelDependency {
dependency_type: DependencyType::Assertive,
dependent_level: ON.level,
requires_token: token_mithril
.duplicate_handle(zx::Rights::SAME_RIGHTS)
.expect("dup failed"),
requires_level_by_preference: vec![40, 30, ON.level, 20],
}],
vec![],
vec![],
)
.expect("add_element failed");
assert_data_tree!(inspect, root: {
test: {
leases: {},
topology: {
"fuchsia.inspect.Graph": {
topology: {
broker.get_unsatisfiable_element_id().to_string() => {
meta: {
name: broker.get_unsatisfiable_element_name(),
valid_levels: broker.get_unsatisfiable_element_levels(),
required_level: "unset",
current_level: "unset",
},
relationships: {}
},
mithril.to_string() => {
meta: {
name: "Mithril",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {},
},
silver.to_string() => {
meta: {
name: "Silver",
valid_levels: v01.clone(),
current_level: OFF.level as u64,
required_level: OFF.level as u64,
},
relationships: {
mithril.to_string() => {
edge_id: AnyProperty,
meta: { "1": "1" },
},
},
},
},
"events": contains {},
}}}});
}
}