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