[pkg-resolver] Cache TUF metadata with non-auto repos
Previously, we would try to update TUF every time before we try to
resolve a package. This can generate a significant amount of load during
an OTA, since we would update for each package. This patch fixes this by
allowing the TUF metadata to be cached for 5 minutes, which should
significantly reduce the load on the server.
Note: This differs from the original CL since fuchsia-pkg-testing's
serve::handler was renamed to serve::responder, and that we needed to
backport serve::handler::Record for the tests to pass.
Fixed: 77442
Change-Id: I70b5a5682a56dd706f26b6536bed13565f6418bd
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/538013
Commit-Queue: Erick Tryzelaar <etryzelaar@google.com>
Fuchsia-Auto-Submit: Erick Tryzelaar <etryzelaar@google.com>
Reviewed-by: Dan Johnson <computerdruid@google.com>
(cherry picked from commit c8a2a422f7684f810af88217fa74ff2d374fdd64)
diff --git a/src/sys/pkg/bin/pkg-resolver/src/repository.rs b/src/sys/pkg/bin/pkg-resolver/src/repository.rs
index 84afc8f..bd71114 100644
--- a/src/sys/pkg/bin/pkg-resolver/src/repository.rs
+++ b/src/sys/pkg/bin/pkg-resolver/src/repository.rs
@@ -300,7 +300,7 @@
http_sse::Event,
matches::assert_matches,
std::sync::Arc,
- updating_tuf_client::SUBSCRIBE_CACHE_STALE_TIMEOUT,
+ updating_tuf_client::METADATA_CACHE_STALE_TIMEOUT,
};
const TEST_REPO_URL: &str = "fuchsia-pkg://test";
@@ -558,7 +558,7 @@
.unwrap();
// cache will now be stale, should trigger an update
- clock::mock::set(initial_time + SUBSCRIBE_CACHE_STALE_TIMEOUT);
+ clock::mock::set(initial_time + METADATA_CACHE_STALE_TIMEOUT);
repo.get_merkle_at_path(&TargetPath::new("just-meta-far/0".to_string()).unwrap())
.await
.unwrap();
@@ -613,6 +613,60 @@
assert!(dir.join("1.root.json").exists());
}
+
+ #[fasync::run_singlethreaded(test)]
+ async fn resolve_caches_metadata() {
+ clock::mock::set(zx::Time::from_nanos(0));
+
+ let pkg = PackageBuilder::new("just-meta-far").build().await.expect("created pkg");
+ let env = TestEnv::builder().add_package(&pkg).build().await;
+
+ let target_path =
+ TargetPath::new("just-meta-far/0".to_string()).expect("created target path");
+
+ let (responder, history) = handler::Record::new();
+ let (_served_repository, repo_config) =
+ env.serve_repo()
+ .uri_path_override_handler(responder)
+ .start(TEST_REPO_URL);
+ let mut repo = env.repo(&repo_config).await.expect("created opened repo");
+
+ // Resolve the package, which should succeed.
+ assert_matches!(repo.get_merkle_at_path(&target_path).await, Ok(_));
+
+ let entries = history.take();
+ let uri_paths = entries.iter().map(|e| e.uri_path().to_str().unwrap()).collect::<Vec<_>>();
+ assert_eq!(
+ uri_paths,
+ vec![
+ "/1.root.json",
+ "/2.root.json",
+ "/timestamp.json",
+ "/2.snapshot.json",
+ "/2.targets.json",
+ ],
+ );
+
+ // Advance time right before the timeout, and make sure we don't access the server.
+ clock::mock::set(
+ zx::Time::from_nanos(0) + METADATA_CACHE_STALE_TIMEOUT - zx::Duration::from_seconds(1),
+ );
+ assert_matches!(repo.get_merkle_at_path(&target_path).await, Ok(_));
+
+ let entries = history.take();
+ assert!(entries.is_empty(), "{:#?}", entries);
+
+ // Advance time right after the timeout, and make sure we access the server.
+ clock::mock::set(
+ zx::Time::from_nanos(0) + METADATA_CACHE_STALE_TIMEOUT + zx::Duration::from_seconds(1),
+ );
+ assert_matches!(repo.get_merkle_at_path(&target_path).await, Ok(_));
+
+ let entries = history.take();
+ assert_eq!(entries.len(), 2, "{:#?}", entries);
+ assert_eq!(entries[0].uri_path(), Path::new("/2.root.json"), "{:#?}", entries);
+ assert_eq!(entries[1].uri_path(), Path::new("/timestamp.json"), "{:#?}", entries);
+ }
}
#[cfg(test)]
diff --git a/src/sys/pkg/bin/pkg-resolver/src/repository/updating_tuf_client.rs b/src/sys/pkg/bin/pkg-resolver/src/repository/updating_tuf_client.rs
index 64683af..4846d5e 100644
--- a/src/sys/pkg/bin/pkg-resolver/src/repository/updating_tuf_client.rs
+++ b/src/sys/pkg/bin/pkg-resolver/src/repository/updating_tuf_client.rs
@@ -44,7 +44,7 @@
last_update_successfully_checked_time: InspectableDebugString<Option<zx::Time>>,
/// `Some` if there is an AutoClient task, dropping it stops the task.
- auto_client_aborter: Option<AbortHandleOnDrop>,
+ _auto_client_aborter: Option<AbortHandleOnDrop>,
tuf_metadata_timeout: Duration,
@@ -144,7 +144,7 @@
&node,
"last_update_successfully_checked_time",
),
- auto_client_aborter,
+ _auto_client_aborter: auto_client_aborter,
tuf_metadata_timeout,
inspect: UpdatingTufClientInspectState {
update_check_failure_count: inspect_util::Counter::new(
@@ -222,11 +222,8 @@
}
fn is_stale(&self) -> bool {
- if self.auto_client_aborter.is_none() {
- return true;
- }
if let Some(last_update_time) = *self.last_update_successfully_checked_time {
- last_update_time + SUBSCRIBE_CACHE_STALE_TIMEOUT <= clock::now()
+ last_update_time + METADATA_CACHE_STALE_TIMEOUT <= clock::now()
} else {
true
}
@@ -273,7 +270,7 @@
}
}
-pub const SUBSCRIBE_CACHE_STALE_TIMEOUT: zx::Duration = zx::Duration::from_minutes(5);
+pub const METADATA_CACHE_STALE_TIMEOUT: zx::Duration = zx::Duration::from_minutes(5);
struct AutoClient {
updating_client: Weak<AsyncMutex<UpdatingTufClient>>,
diff --git a/src/sys/pkg/lib/fuchsia-pkg-testing/src/serve/handler.rs b/src/sys/pkg/lib/fuchsia-pkg-testing/src/serve/handler.rs
index 96a6aa61..50b45b6 100644
--- a/src/sys/pkg/lib/fuchsia-pkg-testing/src/serve/handler.rs
+++ b/src/sys/pkg/lib/fuchsia-pkg-testing/src/serve/handler.rs
@@ -760,3 +760,52 @@
ready(response).boxed()
}
}
+
+/// Information saved by Record for each request it handles.
+#[derive(Debug)]
+pub struct HistoryEntry {
+ uri_path: PathBuf,
+}
+
+impl HistoryEntry {
+ /// The uri_path of the request.
+ pub fn uri_path(&self) -> &Path {
+ &self.uri_path
+ }
+}
+
+/// The request history recorded by Record.
+pub struct History(Arc<Mutex<Vec<HistoryEntry>>>);
+
+impl History {
+ /// Take the recorded history, clearing it from the Record.
+ pub fn take(&self) -> Vec<HistoryEntry> {
+ std::mem::replace(&mut self.0.lock(), vec![])
+ }
+}
+
+/// Responder that records the requests.
+pub struct Record {
+ history: History,
+}
+
+impl Record {
+ /// Creates a responder that records all the requests.
+ pub fn new() -> (Self, History) {
+ let history = Arc::new(Mutex::new(vec![]));
+ (Self { history: History(Arc::clone(&history)) }, History(history))
+ }
+}
+
+impl UriPathHandler for Record {
+ fn handle<'a>(
+ &'a self,
+ uri_path: &Path,
+ response: Response<Body>,
+ ) -> BoxFuture<'_, Response<Body>> {
+ self.history.0.lock().push(HistoryEntry {
+ uri_path: uri_path.to_owned(),
+ });
+ ready(response).boxed()
+ }
+}
diff --git a/src/sys/pkg/tests/pkg-resolver/src/metrics.rs b/src/sys/pkg/tests/pkg-resolver/src/metrics.rs
index 3a89bd5..35b4c7a 100644
--- a/src/sys/pkg/tests/pkg-resolver/src/metrics.rs
+++ b/src/sys/pkg/tests/pkg-resolver/src/metrics.rs
@@ -791,7 +791,6 @@
.uri_path_override_handler(handler::ForPath::new("/2.root.json", handler))
.start()
.unwrap();
- env.register_repo(&served_repository).await;
struct StatusTest {
min_code: u16,
@@ -856,6 +855,12 @@
for ent in test_table.iter() {
for code in ent.min_code..=ent.max_code {
+ // After the package resolver successfully updates TUF, it will automatically cache the
+ // fetched metadata for a period of time in order to reduce load on the repository
+ // server. Since we want each of these resolves to talk to our test server, we'll
+ // re-register the repository before each request in order to reset the timer.
+ env.register_repo(&served_repository).await;
+
response_code.set(code);
let _ = env.resolve_package(&pkg_url).await;
statuses.push(ent.status);