1use super::{
2 ProcessHandle, ProcessObserver, ProcessStatus, ReadinessAttemptEvidence, ReadinessEvidence,
3 ReadinessFailure, ReadinessFailureKind, ReadinessObserver, SystemProcessRuntime,
4 TargetRegistryMatchEvidence,
5};
6use crate::interrupt;
7use crate::operation_bound::{AttemptBound, OperationBound, OperationTerminalCause, Remaining};
8use crate::plan::{ProcessEndpointPlan, ReadinessPlan, TargetRegistryExpectedTarget};
9use std::thread;
10use std::time::Duration;
11
12const POLL_INTERVAL: Duration = Duration::from_millis(100);
13const MAX_PROBE_INTERVAL: Duration = Duration::from_secs(5);
14pub(super) const READINESS_ATTEMPT_CAP: Duration = Duration::from_millis(250);
15
16#[derive(Debug, thiserror::Error)]
17pub(super) enum ReadinessProbeError {
18 #[error("readiness operation deadline expired")]
19 Deadline,
20 #[error("bounded readiness attempt was unexpectedly unbounded")]
21 UnexpectedUnbounded,
22 #[error("{label} request failed: {source}")]
23 Request {
24 label: String,
25 #[source]
26 source: reqwest::Error,
27 },
28 #[error("{label} returned HTTP {status}")]
29 HttpStatus { label: String, status: u16 },
30 #[error("{label} returned invalid JSON: {source}")]
31 InvalidJson {
32 label: String,
33 #[source]
34 source: serde_json::Error,
35 },
36 #[error("target registry observation mismatch: {details}")]
37 RegistryMismatch { details: String },
38}
39
40pub(super) fn ensure_alive(status: ProcessStatus) -> Result<(), ReadinessFailure> {
41 if !status.queried {
42 return Err(readiness_failure(
43 ReadinessFailureKind::Exited,
44 status
45 .error
46 .unwrap_or_else(|| "failed to query server process group".to_owned()),
47 ));
48 }
49 if !status.alive {
50 return Err(readiness_failure(
51 ReadinessFailureKind::Exited,
52 status
53 .error
54 .unwrap_or_else(|| "server process group exited before readiness".to_owned()),
55 ));
56 }
57 Ok(())
58}
59
60fn readiness_failure(kind: ReadinessFailureKind, message: String) -> ReadinessFailure {
61 ReadinessFailure {
62 kind,
63 message,
64 timing: None,
65 diagnostic_attempts: Vec::new(),
66 }
67}
68
69pub(super) fn timed_readiness_failure(
70 mut failure: ReadinessFailure,
71 bound: &OperationBound,
72 terminal_cause: OperationTerminalCause,
73 diagnostic_attempts: Vec<ReadinessAttemptEvidence>,
74) -> ReadinessFailure {
75 failure.timing = Some(bound.timing("before_readiness_wait", terminal_cause));
76 failure.diagnostic_attempts = diagnostic_attempts;
77 failure
78}
79
80pub(super) fn wait_http_ready<R: ProcessObserver>(
81 runtime: &R,
82 handle: &ProcessHandle,
83 endpoint: &ProcessEndpointPlan,
84 path: &str,
85 timeout_seconds: Option<u64>,
86 on_probe_failure: &mut dyn FnMut(&str),
87) -> Result<ReadinessEvidence, ReadinessFailure> {
88 let bound = timeout_seconds
92 .map(|seconds| OperationBound::finite(Duration::from_secs(seconds)))
93 .unwrap_or_else(OperationBound::unbounded);
94 let url = format!("http://{}:{}{}", endpoint.host, endpoint.port, path);
95 let mut attempts = 0_u32;
96 let mut diagnostic_attempts = Vec::new();
97 let mut probe_interval = POLL_INTERVAL;
103 loop {
104 ensure_readiness_active(&bound, timeout_seconds, "no readiness probe completed").map_err(
105 |failure| {
106 timed_readiness_failure(
107 failure,
108 &bound,
109 OperationTerminalCause::TimedOut,
110 diagnostic_attempts.clone(),
111 )
112 },
113 )?;
114 if interrupt::received() {
115 return Err(timed_readiness_failure(
116 readiness_failure(
117 ReadinessFailureKind::Interrupted,
118 "server startup was interrupted".to_owned(),
119 ),
120 &bound,
121 OperationTerminalCause::Interrupted,
122 diagnostic_attempts,
123 ));
124 }
125 let status = runtime.status_with_bound(handle, &bound);
126 ensure_readiness_active(
127 &bound,
128 timeout_seconds,
129 "the server process status attempt did not complete in time",
130 )
131 .map_err(|failure| {
132 timed_readiness_failure(
133 failure,
134 &bound,
135 OperationTerminalCause::TimedOut,
136 diagnostic_attempts.clone(),
137 )
138 })?;
139 if interrupt::received() {
140 return Err(timed_readiness_failure(
141 readiness_failure(
142 ReadinessFailureKind::Interrupted,
143 "server startup was interrupted".to_owned(),
144 ),
145 &bound,
146 OperationTerminalCause::Interrupted,
147 diagnostic_attempts,
148 ));
149 }
150 ensure_alive(status).map_err(|failure| {
151 timed_readiness_failure(
152 failure,
153 &bound,
154 OperationTerminalCause::Failed,
155 diagnostic_attempts.clone(),
156 )
157 })?;
158 attempts = attempts.saturating_add(1);
159 let attempt = probe_http_attempt(&endpoint.host, endpoint.port, path, &bound);
160 let effective_bound_ms = attempt.effective_bound_ms;
161 let last_error = match attempt.outcome {
162 Ok(()) => {
163 diagnostic_attempts = vec![ReadinessAttemptEvidence {
164 operation: "http_readiness".to_owned(),
165 effective_bound_ms,
166 succeeded: true,
167 error: None,
168 }];
169 let ready_unix_ms = unix_time_millis().map_err(|failure| {
170 timed_readiness_failure(
171 failure,
172 &bound,
173 OperationTerminalCause::Failed,
174 diagnostic_attempts.clone(),
175 )
176 })?;
177 ensure_readiness_active(
178 &bound,
179 timeout_seconds,
180 "the readiness response completed after the deadline",
181 )
182 .map_err(|failure| {
183 timed_readiness_failure(
184 failure,
185 &bound,
186 OperationTerminalCause::TimedOut,
187 diagnostic_attempts.clone(),
188 )
189 })?;
190 return Ok(ReadinessEvidence::Http {
191 url,
192 attempts,
193 ready_unix_ms,
194 timing: bound
195 .timing("before_readiness_wait", OperationTerminalCause::Succeeded),
196 diagnostic_attempts,
197 });
198 }
199 Err(error) => {
200 let error = error.to_string();
201 diagnostic_attempts = vec![ReadinessAttemptEvidence {
202 operation: "http_readiness".to_owned(),
203 effective_bound_ms,
204 succeeded: false,
205 error: Some(error.clone()),
206 }];
207 error
208 }
209 };
210 on_probe_failure(&last_error);
211 if bound.is_expired() {
212 let timeout_seconds = timeout_seconds.unwrap_or_default();
213 return Err(timed_readiness_failure(
214 readiness_failure(
215 ReadinessFailureKind::Timeout,
216 format!(
217 "server did not become ready within {timeout_seconds} seconds; last probe error: {last_error}"
218 ),
219 ),
220 &bound,
221 OperationTerminalCause::TimedOut,
222 diagnostic_attempts,
223 ));
224 }
225 sleep_within_readiness(&bound, probe_interval);
226 probe_interval = (probe_interval * 2).min(MAX_PROBE_INTERVAL);
227 }
228}
229
230pub(super) struct HttpTargetRegistryProbe<'a> {
231 pub(super) readiness_path: &'a str,
232 pub(super) registry_path: &'a str,
233 pub(super) targets_field: &'a str,
234 pub(super) target_url_field: &'a str,
235 pub(super) target_role_field: &'a str,
236 pub(super) target_healthy_field: &'a str,
237 pub(super) target_bootstrap_port_field: &'a str,
238 pub(super) expected_targets: &'a [TargetRegistryExpectedTarget],
239}
240
241fn sleep_within_readiness(bound: &OperationBound, cadence: Duration) {
242 match bound.remaining() {
243 Remaining::Finite(remaining) => thread::sleep(cadence.min(remaining)),
244 Remaining::Expired => {}
245 Remaining::Unbounded => thread::sleep(cadence),
246 }
247}
248
249fn ensure_readiness_active(
250 bound: &OperationBound,
251 timeout_seconds: Option<u64>,
252 last_error: &str,
253) -> Result<(), ReadinessFailure> {
254 if !bound.is_expired() {
255 return Ok(());
256 }
257 Err(readiness_failure(
258 ReadinessFailureKind::Timeout,
259 format!(
260 "server did not become ready within {} seconds; last probe error: {last_error}",
261 timeout_seconds.unwrap_or_default()
262 ),
263 ))
264}
265
266fn attempt_remaining(attempt: &AttemptBound) -> Result<Duration, ReadinessProbeError> {
267 match attempt.remaining() {
268 Remaining::Finite(remaining) => Ok(remaining),
269 Remaining::Expired => Err(ReadinessProbeError::Deadline),
270 Remaining::Unbounded => Err(ReadinessProbeError::UnexpectedUnbounded),
271 }
272}
273
274pub(super) fn wait_http_target_registry_ready(
275 status: impl Fn(&OperationBound) -> ProcessStatus,
276 endpoint: &ProcessEndpointPlan,
277 probe: HttpTargetRegistryProbe<'_>,
278 timeout_seconds: Option<u64>,
279 on_probe_failure: &mut dyn FnMut(&str),
280) -> Result<ReadinessEvidence, ReadinessFailure> {
281 let bound = timeout_seconds
282 .map(|seconds| OperationBound::finite(Duration::from_secs(seconds)))
283 .unwrap_or_else(OperationBound::unbounded);
284 let readiness_url = format!(
285 "http://{}:{}{}",
286 endpoint.host, endpoint.port, probe.readiness_path
287 );
288 let registry_url = format!(
289 "http://{}:{}{}",
290 endpoint.host, endpoint.port, probe.registry_path
291 );
292 let mut attempts = 0_u32;
293 let mut diagnostic_attempts = Vec::new();
294 let mut probe_interval = POLL_INTERVAL;
295 loop {
296 ensure_readiness_active(&bound, timeout_seconds, "no readiness probe completed").map_err(
297 |failure| {
298 timed_readiness_failure(
299 failure,
300 &bound,
301 OperationTerminalCause::TimedOut,
302 diagnostic_attempts.clone(),
303 )
304 },
305 )?;
306 if interrupt::received() {
307 return Err(timed_readiness_failure(
308 readiness_failure(
309 ReadinessFailureKind::Interrupted,
310 "server startup was interrupted".to_owned(),
311 ),
312 &bound,
313 OperationTerminalCause::Interrupted,
314 diagnostic_attempts,
315 ));
316 }
317 let process_status = status(&bound);
318 ensure_readiness_active(
319 &bound,
320 timeout_seconds,
321 "the server process status attempt did not complete in time",
322 )
323 .map_err(|failure| {
324 timed_readiness_failure(
325 failure,
326 &bound,
327 OperationTerminalCause::TimedOut,
328 diagnostic_attempts.clone(),
329 )
330 })?;
331 if interrupt::received() {
332 return Err(timed_readiness_failure(
333 readiness_failure(
334 ReadinessFailureKind::Interrupted,
335 "server startup was interrupted".to_owned(),
336 ),
337 &bound,
338 OperationTerminalCause::Interrupted,
339 diagnostic_attempts,
340 ));
341 }
342 ensure_alive(process_status).map_err(|failure| {
343 timed_readiness_failure(
344 failure,
345 &bound,
346 OperationTerminalCause::Failed,
347 diagnostic_attempts.clone(),
348 )
349 })?;
350 attempts = attempts.saturating_add(1);
351 let public_attempt =
352 probe_http_attempt(&endpoint.host, endpoint.port, probe.readiness_path, &bound);
353 let public_effective_bound_ms = public_attempt.effective_bound_ms;
354 let last_error = match public_attempt.outcome {
355 Ok(()) => {
356 let registry_attempt =
357 probe_target_registry_attempt(&endpoint.host, endpoint.port, &probe, &bound);
358 let registry_effective_bound_ms = registry_attempt.effective_bound_ms;
359 match registry_attempt.outcome {
360 Ok(matched_targets) => {
361 diagnostic_attempts = vec![
362 ReadinessAttemptEvidence {
363 operation: "public_http_readiness".to_owned(),
364 effective_bound_ms: public_effective_bound_ms,
365 succeeded: true,
366 error: None,
367 },
368 ReadinessAttemptEvidence {
369 operation: "target_registry".to_owned(),
370 effective_bound_ms: registry_effective_bound_ms,
371 succeeded: true,
372 error: None,
373 },
374 ];
375 let ready_unix_ms = unix_time_millis().map_err(|failure| {
376 timed_readiness_failure(
377 failure,
378 &bound,
379 OperationTerminalCause::Failed,
380 diagnostic_attempts.clone(),
381 )
382 })?;
383 ensure_readiness_active(
384 &bound,
385 timeout_seconds,
386 "the target registry response completed after the deadline",
387 )
388 .map_err(|failure| {
389 timed_readiness_failure(
390 failure,
391 &bound,
392 OperationTerminalCause::TimedOut,
393 diagnostic_attempts.clone(),
394 )
395 })?;
396 return Ok(ReadinessEvidence::HttpTargetRegistry {
397 readiness_url,
398 registry_url,
399 attempts,
400 ready_unix_ms,
401 matched_targets,
402 timing: bound
403 .timing("before_readiness_wait", OperationTerminalCause::Succeeded),
404 diagnostic_attempts,
405 });
406 }
407 Err(error) => {
408 let error = error.to_string();
409 diagnostic_attempts = vec![
410 ReadinessAttemptEvidence {
411 operation: "public_http_readiness".to_owned(),
412 effective_bound_ms: public_effective_bound_ms,
413 succeeded: true,
414 error: None,
415 },
416 ReadinessAttemptEvidence {
417 operation: "target_registry".to_owned(),
418 effective_bound_ms: registry_effective_bound_ms,
419 succeeded: false,
420 error: Some(error.clone()),
421 },
422 ];
423 error
424 }
425 }
426 }
427 Err(error) => {
428 let error = error.to_string();
429 diagnostic_attempts = vec![ReadinessAttemptEvidence {
430 operation: "public_http_readiness".to_owned(),
431 effective_bound_ms: public_effective_bound_ms,
432 succeeded: false,
433 error: Some(error.clone()),
434 }];
435 format!("public readiness probe failed: {error}")
436 }
437 };
438 on_probe_failure(&last_error);
439 if bound.is_expired() {
440 let timeout_seconds = timeout_seconds.unwrap_or_default();
441 return Err(timed_readiness_failure(
442 readiness_failure(
443 ReadinessFailureKind::Timeout,
444 format!(
445 "server did not become ready within {timeout_seconds} seconds; last probe error: {last_error}"
446 ),
447 ),
448 &bound,
449 OperationTerminalCause::TimedOut,
450 diagnostic_attempts,
451 ));
452 }
453 sleep_within_readiness(&bound, probe_interval);
454 probe_interval = (probe_interval * 2).min(MAX_PROBE_INTERVAL);
455 }
456}
457
458fn probe_target_registry_attempt(
459 host: &str,
460 port: u16,
461 probe: &HttpTargetRegistryProbe<'_>,
462 bound: &OperationBound,
463) -> ProbeAttempt<Vec<TargetRegistryMatchEvidence>> {
464 let response =
465 probe_http_json_attempt(host, port, probe.registry_path, "target registry", bound);
466 let effective_bound_ms = response.effective_bound_ms;
467 let outcome = response
468 .outcome
469 .and_then(|response| match_target_registry(&response, probe, bound));
470 ProbeAttempt {
471 effective_bound_ms,
472 outcome,
473 }
474}
475
476pub(super) fn match_target_registry(
477 response: &serde_json::Value,
478 probe: &HttpTargetRegistryProbe<'_>,
479 bound: &OperationBound,
480) -> Result<Vec<TargetRegistryMatchEvidence>, ReadinessProbeError> {
481 readiness_remaining(bound)?;
482 let targets = response
483 .get(probe.targets_field)
484 .and_then(serde_json::Value::as_array)
485 .ok_or_else(|| ReadinessProbeError::RegistryMismatch {
486 details: format!(
487 "target registry response has no array field {:?}",
488 probe.targets_field
489 ),
490 })?;
491 let mut evidence = Vec::with_capacity(probe.expected_targets.len());
492 for expected in probe.expected_targets {
493 readiness_remaining(bound)?;
494 let matches: Vec<&serde_json::Map<String, serde_json::Value>> = targets
495 .iter()
496 .filter_map(serde_json::Value::as_object)
497 .filter(|target| {
498 target
499 .get(probe.target_url_field)
500 .and_then(serde_json::Value::as_str)
501 == Some(expected.url.as_str())
502 && target
503 .get(probe.target_role_field)
504 .and_then(serde_json::Value::as_str)
505 == Some(expected.role.as_str())
506 })
507 .collect();
508 let target = match matches.as_slice() {
509 [] => {
510 return Err(ReadinessProbeError::RegistryMismatch {
511 details: format!(
512 "target registry has no {:?} target at {:?}",
513 expected.role, expected.url
514 ),
515 });
516 }
517 [target] => *target,
518 _ => {
519 return Err(ReadinessProbeError::RegistryMismatch {
520 details: format!(
521 "target registry has multiple {:?} targets at {:?}",
522 expected.role, expected.url
523 ),
524 });
525 }
526 };
527 let healthy = target
528 .get(probe.target_healthy_field)
529 .and_then(serde_json::Value::as_bool)
530 .ok_or_else(|| ReadinessProbeError::RegistryMismatch {
531 details: format!(
532 "target registry entry for {:?} at {:?} has no boolean {:?} field",
533 expected.role, expected.url, probe.target_healthy_field
534 ),
535 })?;
536 if !healthy {
537 return Err(ReadinessProbeError::RegistryMismatch {
538 details: format!(
539 "target registry entry for {:?} at {:?} is not healthy",
540 expected.role, expected.url
541 ),
542 });
543 }
544 let bootstrap_port = match target.get(probe.target_bootstrap_port_field) {
545 None | Some(serde_json::Value::Null) => None,
546 Some(value) => {
547 let port = value.as_u64().and_then(|port| u16::try_from(port).ok());
548 Some(port.ok_or_else(|| ReadinessProbeError::RegistryMismatch {
549 details: format!(
550 "target registry entry for {:?} at {:?} has invalid {:?}",
551 expected.role, expected.url, probe.target_bootstrap_port_field
552 ),
553 })?)
554 }
555 };
556 if let Some(expected_port) = expected.bootstrap_port
557 && bootstrap_port != Some(expected_port)
558 {
559 return Err(ReadinessProbeError::RegistryMismatch {
560 details: format!(
561 "target registry entry for {:?} at {:?} has bootstrap port {bootstrap_port:?}, expected {expected_port}",
562 expected.role, expected.url
563 ),
564 });
565 }
566 evidence.push(TargetRegistryMatchEvidence {
567 url: expected.url.clone(),
568 role: expected.role.clone(),
569 healthy,
570 bootstrap_port,
571 });
572 }
573 readiness_remaining(bound)?;
574 Ok(evidence)
575}
576
577struct ProbeAttempt<T> {
578 effective_bound_ms: u64,
579 outcome: Result<T, ReadinessProbeError>,
580}
581
582#[cfg(test)]
583pub(super) fn probe_http(
584 host: &str,
585 port: u16,
586 path: &str,
587 bound: &OperationBound,
588) -> Result<(), ReadinessProbeError> {
589 probe_http_attempt(host, port, path, bound).outcome
590}
591
592fn probe_http_attempt(
593 host: &str,
594 port: u16,
595 path: &str,
596 bound: &OperationBound,
597) -> ProbeAttempt<()> {
598 let attempt = bound.attempt(Some(READINESS_ATTEMPT_CAP));
599 let effective_bound_ms = attempt.configured_ms().unwrap_or_default();
600 let outcome = (|| {
601 let url = format!("http://{host}:{port}{path}");
602 let timeout = attempt_remaining(&attempt)?;
603 let client = reqwest::blocking::Client::builder()
604 .timeout(timeout)
605 .connect_timeout(timeout.min(Duration::from_secs(2)))
606 .redirect(reqwest::redirect::Policy::none())
607 .no_proxy()
608 .build()
609 .map_err(|source| readiness_request_error("readiness", source))?;
610 let response = client
611 .get(&url)
612 .send()
613 .map_err(|source| readiness_request_error("readiness", source))?;
614 let status = response.status().as_u16();
615 if (200..300).contains(&status) {
616 Ok(())
617 } else {
618 Err(ReadinessProbeError::HttpStatus {
619 label: "readiness".to_owned(),
620 status,
621 })
622 }
623 })();
624 ProbeAttempt {
625 effective_bound_ms,
626 outcome,
627 }
628}
629
630#[cfg(test)]
631pub(super) fn probe_http_json(
632 host: &str,
633 port: u16,
634 path: &str,
635 label: &str,
636 bound: &OperationBound,
637) -> Result<serde_json::Value, ReadinessProbeError> {
638 probe_http_json_attempt(host, port, path, label, bound).outcome
639}
640
641fn probe_http_json_attempt(
642 host: &str,
643 port: u16,
644 path: &str,
645 label: &str,
646 bound: &OperationBound,
647) -> ProbeAttempt<serde_json::Value> {
648 let attempt = bound.attempt(Some(READINESS_ATTEMPT_CAP));
649 let effective_bound_ms = attempt.configured_ms().unwrap_or_default();
650 let outcome = (|| {
651 let url = format!("http://{host}:{port}{path}");
652 let timeout = attempt_remaining(&attempt)?;
653 let client = reqwest::blocking::Client::builder()
654 .timeout(timeout)
655 .connect_timeout(timeout.min(Duration::from_secs(2)))
656 .redirect(reqwest::redirect::Policy::none())
657 .no_proxy()
658 .build()
659 .map_err(|source| readiness_request_error(label, source))?;
660 let response = client
661 .get(&url)
662 .send()
663 .map_err(|source| readiness_request_error(label, source))?;
664 let status = response.status().as_u16();
665 if !(200..300).contains(&status) {
666 return Err(ReadinessProbeError::HttpStatus {
667 label: label.to_owned(),
668 status,
669 });
670 }
671 let body = response
672 .bytes()
673 .map_err(|source| readiness_request_error(label, source))?;
674 let value =
675 serde_json::from_slice(&body).map_err(|source| ReadinessProbeError::InvalidJson {
676 label: label.to_owned(),
677 source,
678 })?;
679 readiness_remaining(bound)?;
680 Ok(value)
681 })();
682 ProbeAttempt {
683 effective_bound_ms,
684 outcome,
685 }
686}
687
688fn readiness_request_error(label: &str, source: reqwest::Error) -> ReadinessProbeError {
689 if source.is_timeout() {
690 ReadinessProbeError::Deadline
691 } else {
692 ReadinessProbeError::Request {
693 label: label.to_owned(),
694 source,
695 }
696 }
697}
698
699fn readiness_remaining(bound: &OperationBound) -> Result<(), ReadinessProbeError> {
700 match bound.remaining() {
701 Remaining::Expired => Err(ReadinessProbeError::Deadline),
702 Remaining::Finite(_) | Remaining::Unbounded => Ok(()),
703 }
704}
705
706pub(super) fn unix_time_millis() -> Result<u64, ReadinessFailure> {
707 std::time::SystemTime::now()
708 .duration_since(std::time::UNIX_EPOCH)
709 .map(crate::operation_bound::duration_millis)
710 .map_err(|error| {
711 readiness_failure(
712 ReadinessFailureKind::Exited,
713 format!("system clock is before Unix epoch: {error}"),
714 )
715 })
716}
717
718impl ReadinessObserver for SystemProcessRuntime {
719 fn wait_ready(
720 &self,
721 handle: &ProcessHandle,
722 endpoint: &ProcessEndpointPlan,
723 readiness: &ReadinessPlan,
724 on_probe_failure: &mut dyn FnMut(&str),
725 ) -> Result<ReadinessEvidence, ReadinessFailure> {
726 match readiness {
727 ReadinessPlan::ProcessAlive => {
728 let bound = OperationBound::unbounded();
729 ensure_alive(self.status(handle)).map_err(|failure| {
730 timed_readiness_failure(
731 failure,
732 &bound,
733 OperationTerminalCause::Failed,
734 Vec::new(),
735 )
736 })?;
737 Ok(ReadinessEvidence::ProcessAlive {
738 ready_unix_ms: unix_time_millis().map_err(|failure| {
739 timed_readiness_failure(
740 failure,
741 &bound,
742 OperationTerminalCause::Failed,
743 Vec::new(),
744 )
745 })?,
746 timing: bound.timing(
747 "before_process_alive_check",
748 OperationTerminalCause::Succeeded,
749 ),
750 })
751 }
752 ReadinessPlan::Http {
753 path,
754 timeout_seconds,
755 ..
756 } => wait_http_ready(
757 self,
758 handle,
759 endpoint,
760 path,
761 *timeout_seconds,
762 on_probe_failure,
763 ),
764 ReadinessPlan::HttpTargetRegistry {
765 readiness_path,
766 registry_path,
767 targets_field,
768 target_url_field,
769 target_role_field,
770 target_healthy_field,
771 target_bootstrap_port_field,
772 expected_targets,
773 timeout_seconds,
774 ..
775 } => wait_http_target_registry_ready(
776 |bound| self.status_with_bound(handle, bound),
777 endpoint,
778 HttpTargetRegistryProbe {
779 readiness_path,
780 registry_path,
781 targets_field,
782 target_url_field,
783 target_role_field,
784 target_healthy_field,
785 target_bootstrap_port_field,
786 expected_targets,
787 },
788 *timeout_seconds,
789 on_probe_failure,
790 ),
791 }
792 }
793}