blob: b1cdfc347d2273394a04d3f737a62dca962fe43f [file] [log] [blame]
// Copyright 2019 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 futures::future::BoxFuture;
use futures::prelude::*;
use omaha_client::{
clock,
common::{App, CheckOptions, ProtocolState, UpdateCheckSchedule},
installer::Plan,
policy::{CheckDecision, Policy, PolicyData, PolicyEngine, UpdateDecision},
protocol::request::InstallSource,
request_builder::RequestParams,
};
use std::cmp::max;
use std::time::Duration;
/// We do periodic update check roughly every hour.
const PERIODIC_INTERVAL: Duration = Duration::from_secs(1 * 60 * 60);
/// Wait at least one minute before checking for updates after startup.
const STARTUP_DELAY: Duration = Duration::from_secs(60);
/// Wait 5 minutes before retrying after failed update checks.
const RETRY_DELAY: Duration = Duration::from_secs(5 * 60);
/// The policy implementation for Fuchsia.
pub struct FuchsiaPolicy;
impl Policy for FuchsiaPolicy {
fn compute_next_update_time(
policy_data: &PolicyData,
_apps: &[App],
scheduling: &UpdateCheckSchedule,
protocol_state: &ProtocolState,
) -> UpdateCheckSchedule {
// Use server dictated interval if exists, otherwise default to 5 hours.
let interval = protocol_state.server_dictated_poll_interval.unwrap_or(PERIODIC_INTERVAL);
let mut next_update_time = scheduling.last_update_time + interval;
// If we didn't talk to Omaha in the last update check, the `last_update_time` won't be
// updated, and we need to have to a minimum delay time based on number of consecutive
// failed update checks.
let min_delay = if protocol_state.consecutive_failed_update_checks > 3 {
interval
} else if protocol_state.consecutive_failed_update_checks > 0 {
RETRY_DELAY
} else {
STARTUP_DELAY
};
next_update_time = max(next_update_time, policy_data.current_time + min_delay);
UpdateCheckSchedule {
last_update_time: scheduling.last_update_time,
next_update_window_start: next_update_time,
next_update_time,
}
}
fn update_check_allowed(
policy_data: &PolicyData,
_apps: &[App],
scheduling: &UpdateCheckSchedule,
_protocol_state: &ProtocolState,
check_options: &CheckOptions,
) -> CheckDecision {
// Always allow update check initiated by a user.
if check_options.source == InstallSource::OnDemand {
CheckDecision::Ok(RequestParams {
source: InstallSource::OnDemand,
use_configured_proxies: true,
})
} else if policy_data.current_time >= scheduling.next_update_time {
CheckDecision::Ok(RequestParams {
source: InstallSource::ScheduledTask,
use_configured_proxies: true,
})
} else {
CheckDecision::TooSoon
}
}
fn update_can_start(
_policy_data: &PolicyData,
_proposed_install_plan: &impl Plan,
) -> UpdateDecision {
UpdateDecision::Ok
}
}
/// FuchsiaPolicyEngine just gathers the current time and hands it off to the FuchsiaPolicy as the
/// PolicyData.
pub struct FuchsiaPolicyEngine;
impl PolicyEngine for FuchsiaPolicyEngine {
fn compute_next_update_time(
&mut self,
apps: &[App],
scheduling: &UpdateCheckSchedule,
protocol_state: &ProtocolState,
) -> BoxFuture<'_, UpdateCheckSchedule> {
let schedule = FuchsiaPolicy::compute_next_update_time(
&PolicyData { current_time: clock::now() },
apps,
scheduling,
protocol_state,
);
future::ready(schedule).boxed()
}
fn update_check_allowed(
&mut self,
apps: &[App],
scheduling: &UpdateCheckSchedule,
protocol_state: &ProtocolState,
check_options: &CheckOptions,
) -> BoxFuture<'_, CheckDecision> {
let decision = FuchsiaPolicy::update_check_allowed(
&PolicyData { current_time: clock::now() },
apps,
scheduling,
protocol_state,
check_options,
);
future::ready(decision).boxed()
}
fn update_can_start(
&mut self,
proposed_install_plan: &impl Plan,
) -> BoxFuture<'_, UpdateDecision> {
let decision = FuchsiaPolicy::update_can_start(
&PolicyData { current_time: clock::now() },
proposed_install_plan,
);
future::ready(decision).boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use omaha_client::installer::stub::StubPlan;
use std::time::SystemTime;
#[test]
fn test_compute_next_update_time() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - Duration::from_secs(1234);
let schedule = UpdateCheckSchedule {
last_update_time,
next_update_window_start: SystemTime::UNIX_EPOCH,
next_update_time: SystemTime::UNIX_EPOCH,
};
let result = FuchsiaPolicy::compute_next_update_time(
&policy_data,
&[],
&schedule,
&ProtocolState::default(),
);
let next_update_time = last_update_time + PERIODIC_INTERVAL;
let expected = UpdateCheckSchedule {
last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
assert_eq!(result, expected);
}
#[test]
fn test_compute_next_update_time_startup() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - Duration::from_secs(123456);
let schedule = UpdateCheckSchedule {
last_update_time,
next_update_window_start: SystemTime::UNIX_EPOCH,
next_update_time: SystemTime::UNIX_EPOCH,
};
let result = FuchsiaPolicy::compute_next_update_time(
&policy_data,
&[],
&schedule,
&ProtocolState::default(),
);
let next_update_time = now + STARTUP_DELAY;
let expected = UpdateCheckSchedule {
last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
assert_eq!(result, expected);
}
#[test]
fn test_compute_next_update_time_single_failure() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - Duration::from_secs(123456);
let schedule = UpdateCheckSchedule {
last_update_time,
next_update_window_start: SystemTime::UNIX_EPOCH,
next_update_time: SystemTime::UNIX_EPOCH,
};
let mut protocol_state = ProtocolState::default();
protocol_state.consecutive_failed_update_checks = 1;
let result =
FuchsiaPolicy::compute_next_update_time(&policy_data, &[], &schedule, &protocol_state);
let next_update_time = now + RETRY_DELAY;
let expected = UpdateCheckSchedule {
last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
assert_eq!(result, expected);
}
#[test]
fn test_compute_next_update_time_consecutive_failures() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - Duration::from_secs(123456);
let schedule = UpdateCheckSchedule {
last_update_time,
next_update_window_start: SystemTime::UNIX_EPOCH,
next_update_time: SystemTime::UNIX_EPOCH,
};
let mut protocol_state = ProtocolState::default();
protocol_state.consecutive_failed_update_checks = 4;
let result =
FuchsiaPolicy::compute_next_update_time(&policy_data, &[], &schedule, &protocol_state);
let next_update_time = now + PERIODIC_INTERVAL;
let expected = UpdateCheckSchedule {
last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
assert_eq!(result, expected);
}
#[test]
fn test_server_dictated_poll_interval() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - Duration::from_secs(1234);
let interval = Duration::from_secs(5678);
let next_update_time = last_update_time + interval;
let schedule = UpdateCheckSchedule {
last_update_time,
next_update_window_start: SystemTime::UNIX_EPOCH,
next_update_time: SystemTime::UNIX_EPOCH,
};
let protocol_state = ProtocolState {
server_dictated_poll_interval: Some(interval),
..ProtocolState::default()
};
let result =
FuchsiaPolicy::compute_next_update_time(&policy_data, &[], &schedule, &protocol_state);
let expected = UpdateCheckSchedule {
last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
assert_eq!(result, expected);
}
#[test]
fn test_update_check_allowed_ok() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - PERIODIC_INTERVAL - Duration::from_secs(1);
let next_update_time = last_update_time + PERIODIC_INTERVAL;
let schedule = UpdateCheckSchedule {
last_update_time: last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
let check_options = CheckOptions::default();
let result = FuchsiaPolicy::update_check_allowed(
&policy_data,
&[],
&schedule,
&ProtocolState::default(),
&check_options,
);
let expected = CheckDecision::Ok(RequestParams {
source: check_options.source,
use_configured_proxies: true,
});
assert_eq!(result, expected);
}
#[test]
fn test_update_check_allowed_too_soon() {
let now = clock::now();
let policy_data = PolicyData { current_time: now };
let last_update_time = now - PERIODIC_INTERVAL + Duration::from_secs(1);
let next_update_time = last_update_time + PERIODIC_INTERVAL;
let schedule = UpdateCheckSchedule {
last_update_time: last_update_time,
next_update_window_start: next_update_time,
next_update_time,
};
let result = FuchsiaPolicy::update_check_allowed(
&policy_data,
&[],
&schedule,
&ProtocolState::default(),
&CheckOptions::default(),
);
assert_eq!(result, CheckDecision::TooSoon);
}
#[test]
fn test_update_can_start() {
let policy_data = PolicyData { current_time: clock::now() };
let result = FuchsiaPolicy::update_can_start(&policy_data, &StubPlan);
assert_eq!(result, UpdateDecision::Ok);
}
}