1use crate::source::{LayerRef, LayerSource, SourceError};
37use std::cell::RefCell;
38
39pub struct Mirror {
41 pub label: String,
43 pub source: Box<dyn LayerSource>,
44}
45
46pub struct Mirrors {
52 mirrors: Vec<Mirror>,
53 served_by: RefCell<Option<String>>,
55 attempts: RefCell<Vec<(String, String)>>,
57}
58
59impl Mirrors {
60 pub fn new(mirrors: Vec<Mirror>) -> Self {
61 Mirrors {
62 mirrors,
63 served_by: RefCell::new(None),
64 attempts: RefCell::new(Vec::new()),
65 }
66 }
67
68 pub fn served_by(&self) -> Option<String> {
74 self.served_by.borrow().clone()
75 }
76
77 pub fn attempts(&self) -> Vec<(String, String)> {
79 self.attempts.borrow().clone()
80 }
81
82 pub fn labels(&self) -> Vec<&str> {
83 self.mirrors.iter().map(|m| m.label.as_str()).collect()
84 }
85
86 fn try_each<T>(
93 &self,
94 what: &str,
95 mut f: impl FnMut(&dyn LayerSource) -> Result<T, SourceError>,
96 ) -> Result<T, SourceError> {
97 self.attempts.borrow_mut().clear();
98 if self.mirrors.is_empty() {
99 return Err(SourceError::Transport(
100 "this realm declares no sources at all".into(),
101 ));
102 }
103 for m in &self.mirrors {
104 match f(m.source.as_ref()) {
105 Ok(v) => {
106 *self.served_by.borrow_mut() = Some(m.label.clone());
107 return Ok(v);
108 }
109 Err(e) => {
110 self.attempts
111 .borrow_mut()
112 .push((m.label.clone(), e.to_string()));
113 }
114 }
115 }
116 let tried = self
117 .attempts
118 .borrow()
119 .iter()
120 .map(|(l, e)| format!("\n {l}: {e}"))
121 .collect::<String>();
122 Err(SourceError::Transport(format!(
123 "no source could supply {what}. Tried {} source(s):{tried}",
124 self.mirrors.len()
125 )))
126 }
127}
128
129impl LayerSource for Mirrors {
130 fn fetch_manifest(&self, layer: &LayerRef) -> Result<Vec<u8>, SourceError> {
131 self.try_each("the layer manifest", |s| s.fetch_manifest(layer))
132 }
133
134 fn fetch_blob(&self, digest: &str) -> Result<Vec<u8>, SourceError> {
135 self.try_each(&format!("blob {digest}"), |s| s.fetch_blob(digest))
136 }
137
138 fn fetch_line_status(&self, layer: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
139 self.try_each_optional("the line-status document", |s| s.fetch_line_status(layer))
144 }
145
146 fn fetch_line_index(&self, line: &str) -> Result<Option<Vec<u8>>, SourceError> {
147 self.try_each_optional("the line index", |s| s.fetch_line_index(line))
148 }
149
150 fn fetch_attestations(
151 &self,
152 layer: &LayerRef,
153 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
154 self.try_each("the attestations", |s| s.fetch_attestations(layer))
155 }
156}
157
158impl Mirrors {
159 fn try_each_optional(
163 &self,
164 what: &str,
165 mut f: impl FnMut(&dyn LayerSource) -> Result<Option<Vec<u8>>, SourceError>,
166 ) -> Result<Option<Vec<u8>>, SourceError> {
167 self.attempts.borrow_mut().clear();
168 let mut any_answered = false;
169 for m in &self.mirrors {
170 match f(m.source.as_ref()) {
171 Ok(Some(v)) => {
172 *self.served_by.borrow_mut() = Some(m.label.clone());
173 return Ok(Some(v));
174 }
175 Ok(None) => {
176 any_answered = true;
177 self.attempts
178 .borrow_mut()
179 .push((m.label.clone(), "carries none".into()));
180 }
181 Err(e) => {
182 self.attempts
183 .borrow_mut()
184 .push((m.label.clone(), e.to_string()));
185 }
186 }
187 }
188 if any_answered {
189 return Ok(None);
192 }
193 let tried = self
194 .attempts
195 .borrow()
196 .iter()
197 .map(|(l, e)| format!("\n {l}: {e}"))
198 .collect::<String>();
199 Err(SourceError::Transport(format!(
200 "no source could be reached for {what}:{tried}"
201 )))
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use std::cell::Cell;
209 use std::rc::Rc;
210
211 struct Fake {
212 manifest: Option<Vec<u8>>,
213 err: Option<SourceError>,
214 status: Option<Option<Vec<u8>>>,
215 calls: Rc<Cell<usize>>,
216 }
217
218 fn ok(bytes: &[u8]) -> Box<Fake> {
219 Box::new(Fake {
220 manifest: Some(bytes.to_vec()),
221 err: None,
222 status: Some(Some(bytes.to_vec())),
223 calls: Rc::new(Cell::new(0)),
224 })
225 }
226 fn down(msg: &str) -> Box<Fake> {
227 Box::new(Fake {
228 manifest: None,
229 err: Some(SourceError::Transport(msg.into())),
230 status: None,
231 calls: Rc::new(Cell::new(0)),
232 })
233 }
234 fn empty() -> Box<Fake> {
235 Box::new(Fake {
236 manifest: None,
237 err: Some(SourceError::NotFound("layer".into())),
238 status: Some(None),
239 calls: Rc::new(Cell::new(0)),
240 })
241 }
242
243 impl LayerSource for Fake {
244 fn fetch_manifest(&self, _l: &LayerRef) -> Result<Vec<u8>, SourceError> {
245 self.calls.set(self.calls.get() + 1);
246 match (&self.manifest, &self.err) {
247 (Some(m), _) => Ok(m.clone()),
248 (None, Some(e)) => Err(clone_err(e)),
249 _ => Err(SourceError::NotFound("x".into())),
250 }
251 }
252 fn fetch_blob(&self, _d: &str) -> Result<Vec<u8>, SourceError> {
253 self.fetch_manifest(&LayerRef::Digest("x".into()))
254 }
255 fn fetch_line_status(&self, _l: &LayerRef) -> Result<Option<Vec<u8>>, SourceError> {
256 self.calls.set(self.calls.get() + 1);
257 match (&self.status, &self.err) {
258 (Some(s), _) => Ok(s.clone()),
259 (None, Some(e)) => Err(clone_err(e)),
260 _ => Ok(None),
261 }
262 }
263 fn fetch_line_index(&self, _line: &str) -> Result<Option<Vec<u8>>, SourceError> {
264 self.fetch_line_status(&LayerRef::Digest("x".into()))
265 }
266 fn fetch_attestations(
267 &self,
268 _l: &LayerRef,
269 ) -> Result<Vec<crate::attestcarry::CarriedAttestation>, SourceError> {
270 self.calls.set(self.calls.get() + 1);
271 match (&self.manifest, &self.err) {
272 (Some(_), _) => Ok(Vec::new()),
273 (None, Some(e)) => Err(clone_err(e)),
274 _ => Ok(Vec::new()),
275 }
276 }
277 }
278
279 fn clone_err(e: &SourceError) -> SourceError {
280 match e {
281 SourceError::NotFound(s) => SourceError::NotFound(s.clone()),
282 SourceError::Transport(s) => SourceError::Transport(s.clone()),
283 other => SourceError::Transport(other.to_string()),
284 }
285 }
286
287 fn mirrors(v: Vec<(&str, Box<Fake>)>) -> Mirrors {
288 Mirrors::new(
289 v.into_iter()
290 .map(|(l, s)| Mirror {
291 label: l.into(),
292 source: s as Box<dyn LayerSource>,
293 })
294 .collect(),
295 )
296 }
297
298 #[test]
300 fn the_first_source_that_answers_serves_the_layer() {
301 let m = mirrors(vec![("primary", ok(b"manifest")), ("mirror", ok(b"other"))]);
302 assert_eq!(
303 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
304 b"manifest".to_vec()
305 );
306 assert_eq!(m.served_by().as_deref(), Some("primary"));
307 }
308
309 #[test]
312 fn an_unreachable_source_falls_through_to_the_next() {
313 let m = mirrors(vec![
314 ("primary", down("dial tcp: no such host")),
315 ("mirror", ok(b"manifest")),
316 ]);
317 assert_eq!(
318 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap(),
319 b"manifest".to_vec()
320 );
321 assert_eq!(m.served_by().as_deref(), Some("mirror"));
322 }
323
324 #[test]
328 fn a_source_that_lacks_the_layer_falls_through_too() {
329 let m = mirrors(vec![("primary", empty()), ("mirror", ok(b"manifest"))]);
330 assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_ok());
331 assert_eq!(m.served_by().as_deref(), Some("mirror"));
332 }
333
334 #[test]
338 fn which_source_served_the_layer_is_reportable() {
339 let m = mirrors(vec![
340 ("oci://primary", down("503")),
341 ("oci://backup", ok(b"m")),
342 ]);
343 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
344 assert_eq!(m.served_by().as_deref(), Some("oci://backup"));
345 let attempts = m.attempts();
346 assert_eq!(attempts.len(), 1);
347 assert_eq!(attempts[0].0, "oci://primary");
348 assert!(attempts[0].1.contains("503"), "{attempts:?}");
349 }
350
351 #[test]
355 fn when_no_source_answers_the_error_names_all_of_them() {
356 let m = mirrors(vec![
357 ("oci://a", down("no such host")),
358 ("oci://b", down("503 Service Unavailable")),
359 ]);
360 let e = m
361 .fetch_manifest(&LayerRef::Digest("d".into()))
362 .expect_err("must fail");
363 let msg = e.to_string();
364 assert!(
365 msg.contains("oci://a") && msg.contains("no such host"),
366 "{msg}"
367 );
368 assert!(msg.contains("oci://b") && msg.contains("503"), "{msg}");
369 assert!(msg.contains("2 source(s)"), "{msg}");
370 assert!(m.served_by().is_none());
371 }
372
373 #[test]
377 fn a_realm_with_no_sources_says_so() {
378 let m = Mirrors::new(Vec::new());
379 let e = m
380 .fetch_manifest(&LayerRef::Digest("d".into()))
381 .expect_err("must fail");
382 assert!(e.to_string().contains("no sources at all"), "{e}");
383 }
384
385 #[test]
389 fn a_source_after_the_one_that_answered_is_not_consulted() {
390 let second = ok(b"second");
391 let counter = Rc::clone(&second.calls);
392 let m = mirrors(vec![("primary", ok(b"first")), ("mirror", second)]);
393 m.fetch_manifest(&LayerRef::Digest("d".into())).unwrap();
394 assert_eq!(counter.get(), 0, "the later source was consulted anyway");
395 }
396
397 #[test]
403 fn every_kind_of_fetch_falls_through_not_only_the_manifest() {
404 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"bytes"))]);
405 assert_eq!(m.fetch_blob("sha256:x").unwrap(), b"bytes".to_vec());
406 assert_eq!(m.served_by().as_deref(), Some("mirror"));
407
408 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"index"))]);
409 assert_eq!(
410 m.fetch_line_index("2026.09").unwrap(),
411 Some(b"index".to_vec())
412 );
413 assert_eq!(m.served_by().as_deref(), Some("mirror"));
414
415 let m = mirrors(vec![("primary", down("503")), ("mirror", ok(b"att"))]);
416 assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_ok());
417 assert_eq!(m.served_by().as_deref(), Some("mirror"));
418 }
419
420 #[test]
424 fn every_kind_of_fetch_refuses_when_no_source_answers() {
425 let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
426 assert!(m.fetch_manifest(&LayerRef::Digest("d".into())).is_err());
427 assert!(m.fetch_blob("sha256:x").is_err());
428 assert!(m.fetch_line_index("2026.09").is_err());
429 assert!(m.fetch_attestations(&LayerRef::Digest("d".into())).is_err());
430 assert!(
431 m.served_by().is_none(),
432 "nothing served, yet a source is named"
433 );
434 }
435
436 #[test]
440 fn the_configured_sources_are_reportable_in_order() {
441 let m = mirrors(vec![("oci://a", ok(b"x")), ("oci://b", ok(b"y"))]);
442 assert_eq!(m.labels(), vec!["oci://a", "oci://b"]);
443 assert_eq!(Mirrors::new(Vec::new()).labels(), Vec::<&str>::new());
444 }
445
446 #[test]
451 fn a_document_no_source_carries_is_absent_not_unreachable() {
452 let m = mirrors(vec![("a", empty()), ("b", empty())]);
453 assert_eq!(
454 m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
455 None
456 );
457 }
458
459 #[test]
462 fn a_document_nobody_could_be_asked_about_is_not_reported_as_absent() {
463 let m = mirrors(vec![("a", down("no such host")), ("b", down("503"))]);
464 let e = m
465 .fetch_line_status(&LayerRef::Digest("d".into()))
466 .expect_err("must not report absence");
467 assert!(e.to_string().contains("could be reached"), "{e}");
468 }
469
470 #[test]
474 fn a_source_carrying_the_document_is_reached_past_one_that_does_not() {
475 let m = mirrors(vec![("a", empty()), ("b", ok(b"status"))]);
476 assert_eq!(
477 m.fetch_line_status(&LayerRef::Digest("d".into())).unwrap(),
478 Some(b"status".to_vec())
479 );
480 assert_eq!(m.served_by().as_deref(), Some("b"));
481 }
482}