blob: 79420ee206d25dc5cea4b3de6668dc7d4a22ae64 [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.
//! This is the Fuchsia Installer implementation that talks to fuchsia.update.installer FIDL API.
use crate::install_plan::FuchsiaInstallPlan;
use anyhow::anyhow;
use fidl::endpoints::create_proxy;
use fidl_fuchsia_update_installer::{
Initiator, InstallerMarker, InstallerProxy, MonitorEvent, MonitorMarker, MonitorOptions,
Options, State,
};
use fuchsia_component::client::connect_to_service;
use fuchsia_zircon as zx;
use futures::future::BoxFuture;
use futures::prelude::*;
use log::info;
use omaha_client::{
installer::{Installer, ProgressObserver},
protocol::request::InstallSource,
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FuchsiaInstallError {
#[error("generic error: {}", _0)]
Failure(anyhow::Error),
#[error("FIDL error: {}", _0)]
FIDL(fidl::Error),
#[error("System update installer failed")]
Installer,
}
impl From<anyhow::Error> for FuchsiaInstallError {
fn from(e: anyhow::Error) -> FuchsiaInstallError {
FuchsiaInstallError::Failure(e)
}
}
impl From<fidl::Error> for FuchsiaInstallError {
fn from(e: fidl::Error) -> FuchsiaInstallError {
FuchsiaInstallError::FIDL(e)
}
}
#[derive(Debug)]
pub struct FuchsiaInstaller {
proxy: InstallerProxy,
}
impl FuchsiaInstaller {
// Unused until temp_installer.rs is removed.
#[allow(dead_code)]
pub fn new() -> Result<Self, anyhow::Error> {
let proxy = fuchsia_component::client::connect_to_service::<InstallerMarker>()?;
Ok(FuchsiaInstaller { proxy })
}
#[cfg(test)]
fn new_mock() -> (Self, fidl_fuchsia_update_installer::InstallerRequestStream) {
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<InstallerMarker>().unwrap();
(FuchsiaInstaller { proxy }, stream)
}
}
impl Installer for FuchsiaInstaller {
type InstallPlan = FuchsiaInstallPlan;
type Error = FuchsiaInstallError;
fn perform_install(
&mut self,
install_plan: &FuchsiaInstallPlan,
_observer: Option<&dyn ProgressObserver>,
) -> BoxFuture<'_, Result<(), FuchsiaInstallError>> {
let url = install_plan.url.to_string();
let options = Options {
initiator: Some(match install_plan.install_source {
InstallSource::ScheduledTask => Initiator::Service,
InstallSource::OnDemand => Initiator::User,
}),
};
async move {
let (monitor_proxy, server_end) = create_proxy::<MonitorMarker>()?;
let monitor_options = MonitorOptions { should_notify: Some(true) };
let attempt_id =
self.proxy.start_update(&url, options, server_end, monitor_options).await?;
info!("Update started with attempt id: {}", attempt_id);
let mut stream = monitor_proxy.take_event_stream();
while let Some(event) = stream.try_next().await? {
match event {
MonitorEvent::OnStateEnter { state } => {
// TODO: report progress to ProgressObserver
info!("Installer entered state: {}", state_to_string(state));
match state {
State::Complete => {
return Ok(());
}
State::Fail => {
return Err(FuchsiaInstallError::Installer);
}
_ => {}
}
}
}
}
Err(FuchsiaInstallError::Installer)
}
.boxed()
}
fn perform_reboot(&mut self) -> BoxFuture<'_, Result<(), anyhow::Error>> {
async move {
zx::Status::ok(
connect_to_service::<fidl_fuchsia_device_manager::AdministratorMarker>()?
.suspend(fidl_fuchsia_device_manager::SUSPEND_FLAG_REBOOT)
.await?,
)
.map_err(|e| anyhow!("Suspend error: {}", e))
}
.boxed()
}
}
/// Convert fuchsia.update.installer/State to string for ProgressObserver.
fn state_to_string(state: State) -> &'static str {
match state {
State::Prepare => "Prepare",
State::Download => "Download",
State::Stage => "Stage",
State::Reboot => "Reboot",
State::Finalize => "Finalize",
State::Complete => "Complete",
State::Fail => "Fail",
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_update_installer::InstallerRequest;
use fuchsia_async as fasync;
const TEST_URL: &str = "fuchsia-pkg://fuchsia.com/update/0";
#[fasync::run_singlethreaded(test)]
async fn test_start_update() {
let (mut installer, mut stream) = FuchsiaInstaller::new_mock();
let plan = FuchsiaInstallPlan {
url: TEST_URL.parse().unwrap(),
install_source: InstallSource::OnDemand,
};
let installer_fut = async move {
installer.perform_install(&plan, None).await.unwrap();
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(InstallerRequest::StartUpdate {
url,
options: Options { initiator },
monitor,
monitor_options: MonitorOptions { should_notify },
responder,
}) => {
assert_eq!(url, TEST_URL);
assert_eq!(initiator, Some(Initiator::User));
assert_eq!(should_notify, Some(true));
responder.send("00000000-0000-0000-0000-000000000001").unwrap();
let (_stream, handle) = monitor.into_stream_and_control_handle().unwrap();
handle.send_on_state_enter(State::Complete).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(installer_fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_install_error() {
let (mut installer, mut stream) = FuchsiaInstaller::new_mock();
let plan = FuchsiaInstallPlan {
url: TEST_URL.parse().unwrap(),
install_source: InstallSource::OnDemand,
};
let installer_fut = async move {
match installer.perform_install(&plan, None).await {
Err(FuchsiaInstallError::Installer) => {} // expected
result => panic!("Unexpected result: {:?}", result),
}
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
responder.send("00000000-0000-0000-0000-000000000002").unwrap();
let (_stream, handle) = monitor.into_stream_and_control_handle().unwrap();
handle.send_on_state_enter(State::Fail).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(installer_fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_fidl_error() {
let (mut installer, mut stream) = FuchsiaInstaller::new_mock();
let plan = FuchsiaInstallPlan {
url: TEST_URL.parse().unwrap(),
install_source: InstallSource::OnDemand,
};
let installer_fut = async move {
match installer.perform_install(&plan, None).await {
Err(FuchsiaInstallError::FIDL(_)) => {} // expected
result => panic!("Unexpected result: {:?}", result),
}
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(InstallerRequest::StartUpdate { .. }) => {
// Don't send attempt id.
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(installer_fut, stream_fut).await;
}
#[fasync::run_singlethreaded(test)]
async fn test_server_close_unexpectedly() {
let (mut installer, mut stream) = FuchsiaInstaller::new_mock();
let plan = FuchsiaInstallPlan {
url: TEST_URL.parse().unwrap(),
install_source: InstallSource::OnDemand,
};
let installer_fut = async move {
match installer.perform_install(&plan, None).await {
Err(FuchsiaInstallError::Installer) => {} // expected
result => panic!("Unexpected result: {:?}", result),
}
};
let stream_fut = async move {
match stream.next().await.unwrap() {
Ok(InstallerRequest::StartUpdate { monitor, responder, .. }) => {
responder.send("00000000-0000-0000-0000-000000000003").unwrap();
let (_stream, handle) = monitor.into_stream_and_control_handle().unwrap();
handle.send_on_state_enter(State::Prepare).unwrap();
handle.send_on_state_enter(State::Download).unwrap();
}
request => panic!("Unexpected request: {:?}", request),
}
};
future::join(installer_fut, stream_fut).await;
}
}