nixfleet_state_machine/transitions/
activating.rs1use chrono::{DateTime, Utc};
12use nixfleet_proto::{OnHealthFailure, RolloutPolicy};
13
14use crate::effect::{Effect, OutboundAgentEvent};
15use crate::error::TransitionError;
16use crate::event::Event;
17use crate::state::{HostRolloutState, HostState};
18
19use super::illegal;
20
21pub(super) fn handle(
22 mut state: HostRolloutState,
23 event: Event,
24 _now: DateTime<Utc>,
25 policy: &RolloutPolicy,
26) -> Result<(HostRolloutState, Vec<Effect>), TransitionError> {
27 match event {
28 Event::LocalActivationStarted {
30 started_at,
31 switch_method,
32 seq,
33 } => {
34 state.activation_started_at = Some(started_at);
35 state.last_event_seq = seq;
36 let effects = vec![Effect::LocalEmitEvent {
37 rollout_id: state.rollout_id.clone(),
38 payload: OutboundAgentEvent::ActivationStarted {
39 started_at,
40 switch_method,
41 seq,
42 },
43 durable: true,
44 }];
45 Ok((state, effects))
46 }
47 Event::RemoteActivationStarted {
48 started_at,
49 switch_method,
50 seq,
51 } => {
52 state.activation_started_at = Some(started_at);
53 state.last_event_seq = seq;
54 let effects = vec![Effect::RemoteAppendEventLog {
55 host: state.hostname.clone(),
56 rollout_id: state.rollout_id.clone(),
57 payload: OutboundAgentEvent::ActivationStarted {
58 started_at,
59 switch_method,
60 seq,
61 },
62 }];
63 Ok((state, effects))
64 }
65
66 Event::LocalActivationCompleted {
68 observed_current_closure,
69 exit_code,
70 completed_at,
71 seq,
72 } => {
73 let from = state.state;
74 state.state = HostState::Soaking;
75 state.activation_completed_at = Some(completed_at);
76 state.current_closure = Some(observed_current_closure.clone());
77 state.probes.clear();
78 state.last_event_seq = seq;
79
80 let effects = vec![
81 Effect::LocalResetProbeCache {
82 rollout_id: state.rollout_id.clone(),
83 },
84 Effect::LocalEmitEvent {
85 rollout_id: state.rollout_id.clone(),
86 payload: OutboundAgentEvent::ActivationCompleted {
87 observed_current_closure,
88 exit_code,
89 completed_at,
90 seq,
91 },
92 durable: true,
93 },
94 Effect::RecordTransition {
95 host: state.hostname.clone(),
96 rollout_id: state.rollout_id.clone(),
97 from,
98 to: HostState::Soaking,
99 at: completed_at,
100 },
101 ];
102 Ok((state, effects))
103 }
104 Event::RemoteActivationCompleted {
105 observed_current_closure,
106 exit_code,
107 completed_at,
108 seq,
109 } => {
110 let from = state.state;
111 state.state = HostState::Soaking;
112 state.activation_completed_at = Some(completed_at);
113 state.current_closure = Some(observed_current_closure.clone());
114 state.probes.clear();
115 state.last_event_seq = seq;
116
117 let effects = vec![
118 Effect::RemoteAppendEventLog {
119 host: state.hostname.clone(),
120 rollout_id: state.rollout_id.clone(),
121 payload: OutboundAgentEvent::ActivationCompleted {
122 observed_current_closure,
123 exit_code,
124 completed_at,
125 seq,
126 },
127 },
128 Effect::RecordTransition {
129 host: state.hostname.clone(),
130 rollout_id: state.rollout_id.clone(),
131 from,
132 to: HostState::Soaking,
133 at: completed_at,
134 },
135 ];
136 Ok((state, effects))
137 }
138
139 Event::LocalActivationDeferred {
153 component,
154 deferred_at,
155 seq,
156 } => {
157 let from = state.state;
158 state.state = HostState::Deferred;
159 state.last_event_seq = seq;
160 let effects = vec![
161 Effect::LocalEmitEvent {
162 rollout_id: state.rollout_id.clone(),
163 payload: OutboundAgentEvent::ActivationDeferred {
164 component,
165 deferred_at,
166 seq,
167 },
168 durable: true,
169 },
170 Effect::RecordTransition {
171 host: state.hostname.clone(),
172 rollout_id: state.rollout_id.clone(),
173 from,
174 to: HostState::Deferred,
175 at: deferred_at,
176 },
177 ];
178 Ok((state, effects))
179 }
180
181 Event::RemoteActivationDeferred {
185 component,
186 deferred_at,
187 seq,
188 } => {
189 let from = state.state;
190 state.state = HostState::Deferred;
191 state.last_event_seq = seq;
192 let effects = vec![
193 Effect::RemoteAppendEventLog {
194 host: state.hostname.clone(),
195 rollout_id: state.rollout_id.clone(),
196 payload: OutboundAgentEvent::ActivationDeferred {
197 component,
198 deferred_at,
199 seq,
200 },
201 },
202 Effect::RecordTransition {
203 host: state.hostname.clone(),
204 rollout_id: state.rollout_id.clone(),
205 from,
206 to: HostState::Deferred,
207 at: deferred_at,
208 },
209 ];
210 Ok((state, effects))
211 }
212
213 Event::LocalActivationFailed {
215 exit_code,
216 stderr_tail,
217 failed_at,
218 seq,
219 } => {
220 let from = state.state;
221 state.state = HostState::Failed;
222 state.activation_failed_at = Some(failed_at);
223 state.failed_at = Some(failed_at);
224 state.policy_applied = Some(policy.on_health_failure);
225 state.last_event_seq = seq;
226
227 let mut effects = vec![
228 Effect::LocalEmitEvent {
229 rollout_id: state.rollout_id.clone(),
230 payload: OutboundAgentEvent::ActivationFailed {
231 exit_code,
232 stderr_tail,
233 failed_at,
234 seq,
235 },
236 durable: true,
237 },
238 Effect::RecordTransition {
239 host: state.hostname.clone(),
240 rollout_id: state.rollout_id.clone(),
241 from,
242 to: HostState::Failed,
243 at: failed_at,
244 },
245 ];
246 if matches!(policy.on_health_failure, OnHealthFailure::RollbackAndHalt)
249 && let Some(prior) = state.current_closure_at_dispatch.clone()
250 {
251 effects.push(Effect::LocalFireRollbackTo {
252 rollout_id: state.rollout_id.clone(),
253 closure: prior,
254 });
255 }
256 Ok((state, effects))
257 }
258 Event::RemoteActivationFailed {
259 exit_code,
260 stderr_tail,
261 failed_at,
262 seq,
263 } => {
264 let from = state.state;
265 state.state = HostState::Failed;
266 state.activation_failed_at = Some(failed_at);
267 state.failed_at = Some(failed_at);
268 state.policy_applied = Some(policy.on_health_failure);
269 state.last_event_seq = seq;
270
271 let effects = vec![
272 Effect::RemoteAppendEventLog {
273 host: state.hostname.clone(),
274 rollout_id: state.rollout_id.clone(),
275 payload: OutboundAgentEvent::ActivationFailed {
276 exit_code,
277 stderr_tail,
278 failed_at,
279 seq,
280 },
281 },
282 Effect::RecordTransition {
283 host: state.hostname.clone(),
284 rollout_id: state.rollout_id.clone(),
285 from,
286 to: HostState::Failed,
287 at: failed_at,
288 },
289 ];
290 Ok((state, effects))
291 }
292
293 other => Err(illegal(&state, &other)),
294 }
295}