appcore_ops/
availability.rs1use crate::HealthStatus;
4use appcore_core::RuntimeOperationalMode;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum RuntimeAvailabilityState {
11 Ready,
13 Degraded,
15 Restricted,
17 Isolated,
19 Stopped,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25pub struct RuntimeAvailabilityReport {
26 pub state: RuntimeAvailabilityState,
28 pub liveness: bool,
30 pub local_readiness: bool,
32 pub distributed_readiness: bool,
34 pub write_readiness: bool,
36}
37
38impl RuntimeAvailabilityReport {
39 pub fn evaluate(health: HealthStatus, mode: RuntimeOperationalMode) -> Self {
41 if health == HealthStatus::Stopped {
42 return Self::stopped();
43 }
44 if health == HealthStatus::Restricted {
45 return Self::restricted();
46 }
47 match mode {
48 RuntimeOperationalMode::Isolated => Self {
49 state: RuntimeAvailabilityState::Isolated,
50 liveness: true,
51 local_readiness: true,
52 distributed_readiness: false,
53 write_readiness: false,
54 },
55 RuntimeOperationalMode::Degraded => Self::degraded(true),
56 RuntimeOperationalMode::Starting
57 | RuntimeOperationalMode::Discovering
58 | RuntimeOperationalMode::Syncing => Self::degraded(false),
59 RuntimeOperationalMode::ReadOnly => Self::ready(false),
60 RuntimeOperationalMode::ReadWrite if health == HealthStatus::Degraded => {
61 Self::degraded(true)
62 }
63 RuntimeOperationalMode::ReadWrite => Self::ready(true),
64 }
65 }
66
67 fn ready(writes: bool) -> Self {
68 Self {
69 state: RuntimeAvailabilityState::Ready,
70 liveness: true,
71 local_readiness: true,
72 distributed_readiness: true,
73 write_readiness: writes,
74 }
75 }
76
77 fn degraded(local_ready: bool) -> Self {
78 Self {
79 state: RuntimeAvailabilityState::Degraded,
80 liveness: true,
81 local_readiness: local_ready,
82 distributed_readiness: false,
83 write_readiness: false,
84 }
85 }
86
87 fn restricted() -> Self {
88 Self {
89 state: RuntimeAvailabilityState::Restricted,
90 liveness: true,
91 local_readiness: false,
92 distributed_readiness: false,
93 write_readiness: false,
94 }
95 }
96
97 fn stopped() -> Self {
98 Self {
99 state: RuntimeAvailabilityState::Stopped,
100 liveness: false,
101 local_readiness: false,
102 distributed_readiness: false,
103 write_readiness: false,
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn read_write_health_is_fully_ready() {
114 let report = RuntimeAvailabilityReport::evaluate(
115 HealthStatus::Healthy,
116 RuntimeOperationalMode::ReadWrite,
117 );
118 assert_eq!(report.state, RuntimeAvailabilityState::Ready);
119 assert!(report.liveness);
120 assert!(report.local_readiness);
121 assert!(report.distributed_readiness);
122 assert!(report.write_readiness);
123 }
124
125 #[test]
126 fn isolated_runtime_keeps_only_local_reads_ready() {
127 let report = RuntimeAvailabilityReport::evaluate(
128 HealthStatus::Healthy,
129 RuntimeOperationalMode::Isolated,
130 );
131 assert_eq!(report.state, RuntimeAvailabilityState::Isolated);
132 assert!(report.liveness);
133 assert!(report.local_readiness);
134 assert!(!report.distributed_readiness);
135 assert!(!report.write_readiness);
136 }
137
138 #[test]
139 fn restricted_and_stopped_states_fail_readiness() {
140 let restricted = RuntimeAvailabilityReport::evaluate(
141 HealthStatus::Restricted,
142 RuntimeOperationalMode::ReadWrite,
143 );
144 let stopped = RuntimeAvailabilityReport::evaluate(
145 HealthStatus::Stopped,
146 RuntimeOperationalMode::ReadWrite,
147 );
148 assert!(restricted.liveness);
149 assert!(!restricted.local_readiness);
150 assert!(!stopped.liveness);
151 }
152}