blob: e14430d08961465a58e05434961a3f2190be706c [file] [log] [blame]
// Copyright 2021 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::core::collection::{Components, ManifestData, Manifests, Sysmgr},
anyhow,
scrutiny::{
collectors, controllers,
engine::{
hook::PluginHooks,
plugin::{Plugin, PluginDescriptor},
},
model::collector::DataCollector,
model::controller::DataController,
model::model::DataModel,
plugin,
},
serde::{Deserialize, Serialize},
serde_json::{self, value::Value},
std::{
collections::{HashMap, HashSet},
sync::Arc,
},
};
#[derive(Default)]
pub struct FindSysRealmComponents {}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash)]
struct ComponentManifest {
pub url: String,
pub manifest: String,
pub features: ManifestContent,
}
#[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Eq, Hash)]
struct ManifestContent {
features: Option<Vec<String>>,
}
impl DataController for FindSysRealmComponents {
fn query(&self, model: Arc<DataModel>, _query: Value) -> Result<Value, anyhow::Error> {
// 1. Identify the URLs that provide services in the sys realm
// 2. Find the data model's IDs for the components
// 3. Using the component IDs, find the manifest for the providing components
let mut component_urls = HashSet::<String>::new();
let sysmgr = model.get::<Sysmgr>()?;
for svc in sysmgr.services.keys() {
component_urls.insert(sysmgr.services.get(svc).unwrap().clone());
}
let mut component_ids = HashMap::<i32, String>::new();
let components = model.get::<Components>()?;
for c in &components.entries {
if component_urls.contains(&c.url) {
component_ids.insert(c.id, c.url.clone());
}
}
let mut component_manifests = HashMap::<i32, ComponentManifest>::new();
let manifests = model.get::<Manifests>()?;
for manifest in &manifests.entries {
component_ids.get(&manifest.component_id).map(|url| {
let manifest_str = match &manifest.manifest {
ManifestData::Version1(content) => content.clone(),
ManifestData::Version2(content) => content.clone(),
};
let features = serde_json::from_str::<ManifestContent>(&manifest_str).ok()?;
component_manifests.insert(
manifest.component_id,
ComponentManifest {
url: url.clone(),
manifest: manifest_str,
features: features,
},
);
Some(url)
});
}
let mut feature_index = HashMap::<String, Vec<ComponentManifest>>::new();
for manifest in component_manifests.values() {
match &manifest.features.features {
Some(features) => {
for feature in features {
if let Some(f) = feature_index.get_mut(feature) {
f.push(manifest.clone());
} else {
feature_index.insert(feature.clone(), vec![manifest.clone()]);
}
}
}
None => {
if let Some(f) = feature_index.get_mut(&"".to_string()) {
f.push(manifest.clone());
} else {
feature_index.insert("".to_string(), vec![manifest.clone()]);
}
}
}
}
Ok(serde_json::to_value(
component_manifests.values().into_iter().collect::<Vec<&ComponentManifest>>(),
)?)
}
fn description(&self) -> String {
String::from("Finds the components that live in the v1 sys realm")
}
}
plugin!(
SysRealmPlugin,
PluginHooks::new(
collectors! {},
controllers! {
"/sys/realm" => FindSysRealmComponents::default(),
}
),
vec![PluginDescriptor::new("CorePlugin")]
);
#[cfg(test)]
mod tests {
use {
super::{ComponentManifest, FindSysRealmComponents, ManifestContent},
crate::core::{
package::{
collector::{self, PackageDataCollector},
test_utils::{self, MockPackageReader},
},
util::{
jsons::{Custom, FarPackageDefinition, Signed, TargetsJson},
types::{ComponentV1Manifest, PackageDefinition},
},
},
scrutiny::model::{collector::DataCollector, controller::DataController},
serde_json,
std::{
collections::{HashMap, HashSet},
sync::Arc,
},
};
struct SysRealmProvider {
pkg_name: String,
manifest_path: String,
service_name: String,
config_path: String,
config_content: String,
sandbox: ComponentV1Manifest,
serialized_sandbox: String,
}
impl SysRealmProvider {
pub fn new(
pkg_name: String,
manifest_path: String,
service_name: String,
config_path: String,
config_content: String,
sandbox: ComponentV1Manifest,
) -> Self {
Self {
pkg_name,
manifest_path,
service_name,
config_path,
config_content,
serialized_sandbox: serde_json::to_string::<ComponentV1Manifest>(&sandbox).unwrap(),
sandbox,
}
}
}
impl From<&SysRealmProvider> for PackageDefinition {
fn from(src: &SysRealmProvider) -> PackageDefinition {
let src_manifests = test_utils::create_test_cmx_map(vec![(
src.manifest_path.clone(),
src.sandbox.clone(),
)]);
test_utils::create_test_package_with_cms(src.pkg_name.clone(), src_manifests)
}
}
#[test]
fn test_regular_sys_realm() {
let foo_provider = SysRealmProvider::new(
String::from("fuchsia-pkg://fuchsia.com/foo"),
String::from("meta/foo-server.cmx"),
String::from("fuchsia.test.service.foo"),
String::from("data/sysmgr/foo.config"),
String::from(
r#"{
"services": {"fuchsia.test.service.foo": "fuchsia-pkg://fuchsia.com/foo#meta/foo-server.cmx"}
}"#,
),
ComponentV1Manifest {
dev: None,
services: None,
system: None,
pkgfs: None,
features: Some(vec![String::from("isolated-temp")]),
},
);
let bar_provider = SysRealmProvider::new(
String::from("fuchsia-pkg://fuchsia.com/bar"),
String::from("meta/bar-server.cmx"),
String::from("fuchsia.test.service.bar"),
String::from("data/sysmgr/bar.config"),
String::from(
r#"{
"services": {"fuchsia.test.service.bar": "fuchsia-pkg://fuchsia.com/bar#meta/bar-server.cmx"}
}"#,
),
ComponentV1Manifest {
dev: None,
services: None,
system: None,
pkgfs: None,
features: None,
},
);
let buzz_provider = SysRealmProvider::new(
String::from("fuchsia-pkg://fuchsia.com/buzz"),
String::from("meta/buzzer.cmx"),
String::from("fuchsia.test.service.buzzit"),
String::from("data/sysmgr/buzz.config"),
String::from(
r#"{
"services": {"fuchsia.test.service.buzzit": "fuchsia-pkg://fuchsia.com/buzz#meta/buzzer.cmx"}
}"#,
),
ComponentV1Manifest {
dev: None,
services: None,
system: None,
pkgfs: None,
features: Some(vec![
String::from("isolated-temp"),
String::from("vulkan"),
String::from("isolated-cache"),
]),
},
);
// Given these package descriptions, we expect our data controller to
// produce output that looks like this.
let mut expected = {
let mut manifests = HashSet::<ComponentManifest>::new();
manifests.insert(ComponentManifest {
url: format!(
"{}#{}",
foo_provider.pkg_name.clone(),
foo_provider.manifest_path.clone()
),
features: ManifestContent { features: Some(vec![String::from("isolated-temp")]) },
manifest: foo_provider.serialized_sandbox.clone(),
});
manifests.insert(ComponentManifest {
url: format!(
"{}#{}",
bar_provider.pkg_name.clone(),
bar_provider.manifest_path.clone()
),
features: ManifestContent { features: None },
manifest: bar_provider.serialized_sandbox.clone(),
});
manifests.insert(ComponentManifest {
url: format!(
"{}#{}",
buzz_provider.pkg_name.clone(),
buzz_provider.manifest_path.clone()
),
features: ManifestContent {
features: Some(vec![
String::from("isolated-temp"),
String::from("vulkan"),
String::from("isolated-cache"),
]),
},
manifest: buzz_provider.serialized_sandbox.clone(),
});
manifests
};
let mock = MockPackageReader::new();
// create the service package definitions
let foo_services = vec![(
foo_provider.service_name.clone(),
format!("{}#{}", foo_provider.pkg_name.clone(), foo_provider.manifest_path.clone()),
)];
mock.append_service_pkg_def(test_utils::create_svc_pkg_def(foo_services));
let bar_services = vec![(
bar_provider.service_name.clone(),
format!("{}#{}", bar_provider.pkg_name.clone(), bar_provider.manifest_path.clone()),
)];
mock.append_service_pkg_def(test_utils::create_svc_pkg_def(bar_services));
let buzz_services = vec![(
buzz_provider.service_name.clone(),
format!("{}#{}", buzz_provider.pkg_name.clone(), buzz_provider.manifest_path.clone()),
)];
mock.append_service_pkg_def(test_utils::create_svc_pkg_def(buzz_services));
// Create a config-data package that has appropriate entries for the
// sysmgr package
let mut meta_map = HashMap::new();
meta_map.insert(foo_provider.config_path.clone(), foo_provider.config_content.clone());
meta_map.insert(bar_provider.config_path.clone(), bar_provider.config_content.clone());
meta_map.insert(buzz_provider.config_path.clone(), buzz_provider.config_content.clone());
let config_data = test_utils::create_test_package_with_meta(
String::from(collector::CONFIG_DATA_PKG_URL),
meta_map,
);
mock.append_pkg_def(config_data);
// Create the package for the service providers
mock.append_pkg_def((&foo_provider).into());
mock.append_pkg_def((&bar_provider).into());
mock.append_pkg_def((&buzz_provider).into());
// Create targets for the "foo", "bar", and "config-data" packages
{
let mut targets = HashMap::new();
targets.insert(
String::from(collector::CONFIG_DATA_PKG_URL),
FarPackageDefinition {
custom: Custom { merkle: String::from(collector::CONFIG_DATA_PKG_URL) },
},
);
targets.insert(
foo_provider.pkg_name.clone(),
FarPackageDefinition { custom: Custom { merkle: foo_provider.pkg_name.clone() } },
);
targets.insert(
bar_provider.pkg_name.clone(),
FarPackageDefinition { custom: Custom { merkle: bar_provider.pkg_name.clone() } },
);
targets.insert(
buzz_provider.pkg_name.clone(),
FarPackageDefinition { custom: Custom { merkle: buzz_provider.pkg_name.clone() } },
);
mock.append_target(TargetsJson { signed: Signed { targets: targets } });
}
// With all the data created, send it to the PackageDataCollector
let (_unknown, model) = test_utils::create_model();
let pkg_collector = PackageDataCollector::new_with_reader(Box::new(mock));
pkg_collector.collect(Arc::clone(&model)).unwrap();
// Now run the model through our data controller
let sys_realm = FindSysRealmComponents {};
let actual_sys_realm = serde_json::from_value::<Vec<ComponentManifest>>(
sys_realm.query(model.clone(), "".into()).unwrap(),
)
.unwrap();
// Remove everything in `actual_sys_realm` that appears in the expected
// output while also removing from `expected`.
let actual_sys_realm = actual_sys_realm
.into_iter()
.filter(|actual| !expected.remove(actual))
.collect::<Vec<ComponentManifest>>();
// It should be the case that everything we found in the sys realm was
// in the `expected` map and therefore should have been filtered out.
assert_eq!(actual_sys_realm, vec![]);
assert_eq!(expected.into_iter().collect::<Vec<ComponentManifest>>(), vec![]);
}
#[test]
fn test_empty_sys_realm() {
let mock_reader = MockPackageReader::new();
// Create some packages, but ones that aren't in the sys realm
let manifests = test_utils::create_test_cmx_map(vec![(
String::from("meta/cmp1.cmx"),
ComponentV1Manifest {
dev: None,
services: None,
system: None,
pkgfs: None,
features: Some(vec![String::from("config-data")]),
},
)]);
let pkg_def = test_utils::create_test_package_with_cms(
String::from("fuchsia-pkg://fuchsia.com/pkg1"),
manifests,
);
mock_reader.append_pkg_def(pkg_def);
let manifests = test_utils::create_test_cmx_map(vec![(
String::from("meta/cmp2.cmx"),
ComponentV1Manifest {
dev: None,
services: None,
system: None,
pkgfs: None,
features: None,
},
)]);
let pkg_def = test_utils::create_test_package_with_cms(
String::from("fuchsia-pkg://fuchsia.com/pkg2"),
manifests,
);
mock_reader.append_pkg_def(pkg_def);
let mut targets = HashMap::new();
targets.insert(
String::from("fuchsia-pkg://fuchsia.com/pkg1"),
FarPackageDefinition {
custom: Custom { merkle: String::from("fuchsia-pkg://fuchsia.com/pkg1") },
},
);
targets.insert(
String::from("fuchsia-pkg://fuchsia.com/pkg2"),
FarPackageDefinition {
custom: Custom { merkle: String::from("fuchsia-pkg://fuchsia.com/pkg2") },
},
);
mock_reader.append_target(TargetsJson { signed: Signed { targets } });
let (_unused, model) = test_utils::create_model();
let pkg_collector = PackageDataCollector::new_with_reader(Box::new(mock_reader));
pkg_collector.collect(Arc::clone(&model)).unwrap();
let sys_realm = FindSysRealmComponents {};
let actual_sys_realm = serde_json::from_value::<Vec<ComponentManifest>>(
sys_realm.query(model.clone(), "".into()).unwrap(),
)
.unwrap();
assert!(actual_sys_realm.is_empty());
}
}