blob: 95ee74304187326ebeb903cfd08766e065ba027a [file] [log] [blame]
// Copyright 2020 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 crate::common_utils::common::get_proxy_or_connect;
use crate::repository_manager::types::RepositoryOutput;
use anyhow::{format_err, Error};
use fidl_fuchsia_pkg::{RepositoryManagerMarker, RepositoryManagerProxy};
use fidl_fuchsia_pkg_ext::RepositoryConfig;
use fuchsia_syslog::macros::fx_log_info;
use fuchsia_zircon as zx;
use parking_lot::RwLock;
use serde_json::{from_value, to_value, Value};
use std::convert::TryFrom;
/// Facade providing access to RepositoryManager interfaces.
#[derive(Debug)]
pub struct RepositoryManagerFacade {
proxy: RwLock<Option<RepositoryManagerProxy>>,
}
impl RepositoryManagerFacade {
pub fn new() -> Self {
Self { proxy: RwLock::new(None) }
}
#[cfg(test)]
fn new_with_proxy(proxy: RepositoryManagerProxy) -> Self {
Self { proxy: RwLock::new(Some(proxy)) }
}
fn proxy(&self) -> Result<RepositoryManagerProxy, Error> {
get_proxy_or_connect::<RepositoryManagerMarker>(&self.proxy)
}
/// Lists repositories using the repository_manager fidl service.
///
/// Returns a list containing repository info in the format of
/// RepositoryConfig.
pub async fn list_repo(&self) -> Result<Value, Error> {
match self.fetch_repos().await {
Ok(repos) => {
let return_value = to_value(&repos)?;
return Ok(return_value);
}
Err(err) => {
return Err(format_err!("Listing Repositories failed with error {:?}", err))
}
};
}
/// Add a new source to an existing repository.
///
/// params format uses RepositoryConfig, example:
/// {
/// "repo_url": "fuchsia-pkg://example.com",
/// "root_keys":[
/// {
/// "type":"ed25519",
/// "value":"00"
/// }],
/// "mirrors": [
/// {
/// "mirror_url": "http://example.org/",
/// "subscribe": true
/// }],
/// "update_package_url": "fuchsia-pkg://update.example.com/update",
/// "root_version": 1,
/// "root_threshold": 1,
/// }
pub async fn add(&self, args: Value) -> Result<Value, Error> {
let add_request: RepositoryConfig = from_value(args)?;
fx_log_info!("Add Repo request received {:?}", add_request);
let res = self.proxy()?.add(add_request.into()).await?;
match res.map_err(zx::Status::from_raw) {
Ok(()) => Ok(to_value(RepositoryOutput::Success)?),
_ => Err(format_err!("Add repo errored with code {:?}", res)),
}
}
/// Fetches repositories using repository_manager.list FIDL service.
async fn fetch_repos(&self) -> Result<Vec<RepositoryConfig>, anyhow::Error> {
let (iter, server_end) = fidl::endpoints::create_proxy()?;
self.proxy()?.list(server_end)?;
let mut repos = vec![];
loop {
let chunk = iter.next().await?;
if chunk.is_empty() {
break;
}
repos.extend(chunk);
}
repos
.into_iter()
.map(|repo| RepositoryConfig::try_from(repo).map_err(|e| anyhow::Error::from(e)))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common_utils::test::assert_value_round_trips_as;
use fidl_fuchsia_pkg::{RepositoryIteratorRequest, RepositoryManagerRequest};
use fidl_fuchsia_pkg_ext::{
MirrorConfigBuilder, RepositoryConfig, RepositoryConfigBuilder, RepositoryKey,
};
use fuchsia_syslog::macros::{fx_log_err, fx_log_info};
use fuchsia_url::pkg_url::{PkgUrl, RepoUrl};
use futures::{future::Future, join, StreamExt, TryFutureExt, TryStreamExt};
use http::Uri;
use matches::assert_matches;
use parking_lot::Mutex;
use serde_json::json;
use std::iter::FusedIterator;
fn make_test_repo_config() -> RepositoryConfig {
RepositoryConfigBuilder::new(RepoUrl::new("example.com".to_string()).expect("valid url"))
.add_root_key(RepositoryKey::Ed25519(vec![0u8]))
.add_mirror(
MirrorConfigBuilder::new("http://example.org".parse::<Uri>().unwrap())
.unwrap()
.subscribe(true)
.build(),
)
.update_package_url(
PkgUrl::parse("fuchsia-pkg://update.example.com/update").expect("valid PkgUrl"),
)
.build()
}
struct MockRepositoryManagerBuilder {
expected: Vec<Box<dyn FnOnce(RepositoryManagerRequest) + Send + 'static>>,
repos: Mutex<Vec<RepositoryConfig>>,
}
impl MockRepositoryManagerBuilder {
fn new() -> Self {
Self { expected: vec![], repos: Mutex::new(vec![]) }
}
fn push(mut self, request: impl FnOnce(RepositoryManagerRequest) + Send + 'static) -> Self {
self.expected.push(Box::new(request));
self
}
fn add_repository(self, repo_config: RepositoryConfig) -> Self {
self.repos.lock().push(repo_config);
self
}
fn expect_list_repository(self) -> Self {
let mut repos = self.repos.lock().clone().into_iter().map(|r| r.into());
self.push(move |req| match req {
RepositoryManagerRequest::List { iterator, .. } => {
let mut stream = iterator.into_stream().expect("list iterator into_stream");
// repos must be fused b/c the Next() fidl method should return an empty vector
// forever after iteration is complete
let _: &dyn FusedIterator<Item = _> = &repos;
fuchsia_async::Task::spawn(
async move {
while let Some(RepositoryIteratorRequest::Next { responder }) =
stream.try_next().await?
{
responder.send(&mut repos.by_ref().take(5)).expect("next send")
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| {
fx_log_err!("error running list protocol: {:#}", e)
}),
)
.detach();
}
req => panic!("unexpected request: {:?}", req),
})
}
fn expect_add_repository(self, repo_add: RepositoryConfig) -> Self {
self.push(move |req| match req {
RepositoryManagerRequest::Add { repo, responder } => {
let new_repo = RepositoryConfig::try_from(repo).expect("valid repo config");
assert_eq!(new_repo, repo_add);
responder.send(&mut Ok(())).expect("send ok");
}
req => panic!("unexpected request: {:?}", req),
})
}
fn build(self) -> (RepositoryManagerFacade, impl Future<Output = ()>) {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<RepositoryManagerMarker>().unwrap();
let fut = async move {
for expected in self.expected {
expected(stream.next().await.unwrap().unwrap());
}
assert_matches!(stream.next().await, None);
};
(RepositoryManagerFacade::new_with_proxy(proxy), fut)
}
}
#[test]
fn serde_repo_configuration() {
let repo_config = make_test_repo_config();
assert_value_round_trips_as(
repo_config,
json!(
{
"repo_url": "fuchsia-pkg://example.com",
"root_keys":[
{
"type":"ed25519",
"value":"00"
}],
"mirrors": [
{
"mirror_url": "http://example.org/",
"subscribe": true
}],
"update_package_url": "fuchsia-pkg://update.example.com/update",
"root_version": 1,
"root_threshold": 1,
"use_local_mirror": false,
"repo_storage_type": "ephemeral",
}),
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn list_repository_ok() {
let (facade, repository_manager) = MockRepositoryManagerBuilder::new()
.add_repository(make_test_repo_config())
.expect_list_repository()
.build();
let test = async move {
let config = facade.list_repo().await.unwrap();
fx_log_info!("Repo listed: {:?}", config);
let mut repo_config: Vec<RepositoryConfig> = from_value(config).unwrap();
assert_eq!(repo_config.len(), 1);
let received_repo = repo_config.pop().unwrap();
let expected_pkg_url =
PkgUrl::parse("fuchsia-pkg://update.example.com/update").unwrap();
match received_repo.update_package_url() {
Some(u) => assert_eq!(u.to_string(), expected_pkg_url.to_string()),
None => fx_log_err!("update_package_url is empty."),
}
};
join!(repository_manager, test);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn add_repository_ok() {
let repo_test = make_test_repo_config();
let (facade, repository_manager) =
MockRepositoryManagerBuilder::new().expect_add_repository(repo_test.clone()).build();
let test = async move {
let status = facade.add(to_value(repo_test.clone()).unwrap()).await.unwrap();
assert_matches!(from_value(status).unwrap(), RepositoryOutput::Success)
};
join!(repository_manager, test);
}
}