• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 966621741

pending completion
966621741

push

gitlab-ci

vpodzime
feat: Add signal handling to mender-update::deamon::StateMachine

Handling of SIGUSR1 and SIGUSR2 triggering update check and
inventory push respectively and SIGTERM, SIGINT and SIGQUIT
triggering clean shutdown (stopping the main loop).

Ticket: MEN-6564
Changelog: none
Signed-off-by: Vratislav Podzimek <v.podzimek@mykolab.com>

24 of 24 new or added lines in 2 files covered. (100.0%)

5280 of 6671 relevant lines covered (79.15%)

200.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.78
/mender-update/daemon/state_machine/state_machine.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <common/log.hpp>
16
#include <mender-update/daemon/states.hpp>
17
#include <mender-update/daemon/state_machine.hpp>
18

19
namespace mender {
20
namespace update {
21
namespace daemon {
22

23
namespace log = mender::common::log;
24

25
StateMachine::StateMachine(Context &ctx, events::EventLoop &event_loop) :
44✔
26
        ctx_(ctx),
27
        event_loop_(event_loop),
28
        check_update_handler_(event_loop),
29
        inventory_update_handler_(event_loop),
30
        termination_handler_(event_loop),
31
        submit_inventory_state_(event_loop),
32
        poll_for_deployment_state_(event_loop),
33
        send_download_status_state_(deployments::DeploymentStatus::Downloading),
×
34
        send_install_status_state_(deployments::DeploymentStatus::Installing),
×
35
        send_reboot_status_state_(deployments::DeploymentStatus::Rebooting),
×
36
        send_commit_status_state_(
37
                deployments::DeploymentStatus::Installing,
×
38
                event_loop,
39
                ctx.mender_context.GetConfig().retry_poll_interval_seconds),
88✔
40
        // nullopt means: Fetch success/failure status from deployment context
41
        send_final_status_state_(
42
                optional::nullopt, event_loop, ctx.mender_context.GetConfig().retry_poll_interval_seconds),
88✔
43
        exit_state_(event_loop),
44
        main_states_(idle_state_),
45
        runner_(ctx) {
44✔
46
        runner_.AddStateMachine(main_states_);
44✔
47
        runner_.AddStateMachine(deployment_tracking_.states_);
44✔
48

49
        runner_.AttachToEventLoop(event_loop_);
44✔
50

51
        using se = StateEvent;
52
        using tf = sm::TransitionFlag;
53

54
        // When updating the table below, make sure that the initial states are in sync as well, in
55
        // LoadStateFromDb().
56

57
        // clang-format off
58
        main_states_.AddTransition(idle_state_,                          se::DeploymentPollingTriggered, poll_for_deployment_state_,           tf::Deferred );
44✔
59
        main_states_.AddTransition(idle_state_,                          se::InventoryPollingTriggered,  submit_inventory_state_,              tf::Deferred );
44✔
60

61
        main_states_.AddTransition(submit_inventory_state_,              se::Success,                    idle_state_,                          tf::Immediate);
44✔
62
        main_states_.AddTransition(submit_inventory_state_,              se::Failure,                    idle_state_,                          tf::Immediate);
44✔
63

64
        main_states_.AddTransition(poll_for_deployment_state_,           se::Success,                    send_download_status_state_,          tf::Immediate);
44✔
65
        main_states_.AddTransition(poll_for_deployment_state_,           se::NothingToDo,                idle_state_,                          tf::Immediate);
44✔
66
        main_states_.AddTransition(poll_for_deployment_state_,           se::Failure,                    idle_state_,                          tf::Immediate);
44✔
67

68
        // Cannot fail due to FailureMode::Ignore.
69
        main_states_.AddTransition(send_download_status_state_,          se::Success,                    update_download_state_,               tf::Immediate);
44✔
70

71
        main_states_.AddTransition(update_download_state_,               se::Success,                    send_install_status_state_,           tf::Immediate);
44✔
72
        main_states_.AddTransition(update_download_state_,               se::Failure,                    update_rollback_not_needed_state_,    tf::Immediate);
44✔
73
        // Empty payload
74
        main_states_.AddTransition(update_download_state_,               se::NothingToDo,                update_save_provides_state_,          tf::Immediate);
44✔
75
        main_states_.AddTransition(update_download_state_,               se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
76

77
        // Cannot fail due to FailureMode::Ignore.
78
        main_states_.AddTransition(send_install_status_state_,           se::Success,                    update_install_state_,                tf::Immediate);
44✔
79

80
        main_states_.AddTransition(update_install_state_,                se::Success,                    update_check_reboot_state_,           tf::Immediate);
44✔
81
        main_states_.AddTransition(update_install_state_,                se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
82
        main_states_.AddTransition(update_install_state_,                se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
83

84
        main_states_.AddTransition(update_check_reboot_state_,           se::Success,                    send_reboot_status_state_,            tf::Immediate);
44✔
85
        main_states_.AddTransition(update_check_reboot_state_,           se::NothingToDo,                update_before_commit_state_,          tf::Immediate);
44✔
86
        main_states_.AddTransition(update_check_reboot_state_,           se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
87
        main_states_.AddTransition(update_check_reboot_state_,           se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
88

89
        // Cannot fail due to FailureMode::Ignore.
90
        main_states_.AddTransition(send_reboot_status_state_,            se::Success,                    update_reboot_state_,                 tf::Immediate);
44✔
91

92
        main_states_.AddTransition(update_reboot_state_,                 se::Success,                    update_verify_reboot_state_,          tf::Immediate);
44✔
93
        main_states_.AddTransition(update_reboot_state_,                 se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
94
        main_states_.AddTransition(update_reboot_state_,                 se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
95

96
        main_states_.AddTransition(update_verify_reboot_state_,          se::Success,                    update_before_commit_state_,          tf::Immediate);
44✔
97
        main_states_.AddTransition(update_verify_reboot_state_,          se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
98
        main_states_.AddTransition(update_verify_reboot_state_,          se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
99

100
        // Cannot fail.
101
        main_states_.AddTransition(update_before_commit_state_,          se::Success,                    send_commit_status_state_,            tf::Immediate);
44✔
102

103
        main_states_.AddTransition(send_commit_status_state_,            se::Success,                    update_commit_state_,                 tf::Immediate);
44✔
104
        main_states_.AddTransition(send_commit_status_state_,            se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
105

106
        main_states_.AddTransition(update_commit_state_,                 se::Success,                    update_after_commit_state_,           tf::Immediate);
44✔
107
        main_states_.AddTransition(update_commit_state_,                 se::Failure,                    update_check_rollback_state_,         tf::Immediate);
44✔
108
        main_states_.AddTransition(update_commit_state_,                 se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
109

110
        main_states_.AddTransition(update_after_commit_state_,           se::Success,                    update_save_provides_state_,          tf::Immediate);
44✔
111
        main_states_.AddTransition(update_after_commit_state_,           se::Failure,                    update_save_provides_state_,          tf::Immediate);
44✔
112
        main_states_.AddTransition(update_after_commit_state_,           se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
113

114
        main_states_.AddTransition(update_check_rollback_state_,         se::Success,                    update_rollback_state_,               tf::Immediate);
44✔
115
        main_states_.AddTransition(update_check_rollback_state_,         se::NothingToDo,                update_failure_state_,                tf::Immediate);
44✔
116
        main_states_.AddTransition(update_check_rollback_state_,         se::Failure,                    update_failure_state_,                tf::Immediate);
44✔
117
        main_states_.AddTransition(update_check_rollback_state_,         se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
118

119
        main_states_.AddTransition(update_rollback_state_,               se::Success,                    update_check_rollback_reboot_state_,  tf::Immediate);
44✔
120
        main_states_.AddTransition(update_rollback_state_,               se::Failure,                    update_failure_state_,                tf::Immediate);
44✔
121
        main_states_.AddTransition(update_rollback_state_,               se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
122

123
        main_states_.AddTransition(update_check_rollback_reboot_state_,  se::Success,                    update_rollback_reboot_state_,        tf::Immediate);
44✔
124
        main_states_.AddTransition(update_check_rollback_reboot_state_,  se::NothingToDo,                update_rollback_successful_state_,    tf::Immediate);
44✔
125
        main_states_.AddTransition(update_check_rollback_reboot_state_,  se::Failure,                    update_failure_state_,                tf::Immediate);
44✔
126
        main_states_.AddTransition(update_check_rollback_reboot_state_,  se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
127

128
        // No Failure transition for this state, see comments in handler.
129
        main_states_.AddTransition(update_rollback_reboot_state_,        se::Success,                    update_verify_rollback_reboot_state_, tf::Immediate);
44✔
130
        main_states_.AddTransition(update_rollback_reboot_state_,        se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
131

132
        main_states_.AddTransition(update_verify_rollback_reboot_state_, se::Success,                    update_rollback_successful_state_,    tf::Immediate);
44✔
133
        main_states_.AddTransition(update_verify_rollback_reboot_state_, se::Retry,                      update_rollback_reboot_state_,        tf::Immediate);
44✔
134
        main_states_.AddTransition(update_verify_rollback_reboot_state_, se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
135

136
        main_states_.AddTransition(update_rollback_successful_state_,    se::Success,                    update_failure_state_,                tf::Immediate);
44✔
137

138
        main_states_.AddTransition(update_failure_state_,                se::Success,                    update_save_provides_state_,          tf::Immediate);
44✔
139
        main_states_.AddTransition(update_failure_state_,                se::Failure,                    update_save_provides_state_,          tf::Immediate);
44✔
140
        main_states_.AddTransition(update_failure_state_,                se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
141

142
        main_states_.AddTransition(update_save_provides_state_,          se::Success,                    update_cleanup_state_,                tf::Immediate);
44✔
143
        // Even if this fails, there is nothing we can do at this point.
144
        main_states_.AddTransition(update_save_provides_state_,          se::Failure,                    update_cleanup_state_,                tf::Immediate);
44✔
145
        main_states_.AddTransition(update_save_provides_state_,          se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
146

147
        main_states_.AddTransition(update_rollback_not_needed_state_,    se::Success,                    update_cleanup_state_,                tf::Immediate);
44✔
148

149
        main_states_.AddTransition(update_cleanup_state_,                se::Success,                    send_final_status_state_,             tf::Immediate);
44✔
150
        main_states_.AddTransition(update_cleanup_state_,                se::Failure,                    send_final_status_state_,             tf::Immediate);
44✔
151
        main_states_.AddTransition(update_cleanup_state_,                se::StateLoopDetected,          state_loop_state_,                    tf::Immediate);
44✔
152

153
        main_states_.AddTransition(state_loop_state_,                    se::Success,                    send_final_status_state_,             tf::Immediate);
44✔
154
        main_states_.AddTransition(state_loop_state_,                    se::Failure,                    send_final_status_state_,             tf::Immediate);
44✔
155

156
        main_states_.AddTransition(send_final_status_state_,             se::Success,                    clear_artifact_data_state_,           tf::Immediate);
44✔
157
        main_states_.AddTransition(send_final_status_state_,             se::Failure,                    clear_artifact_data_state_,           tf::Immediate);
44✔
158

159
        main_states_.AddTransition(clear_artifact_data_state_,           se::Success,                    end_of_deployment_state_,             tf::Immediate);
44✔
160
        main_states_.AddTransition(clear_artifact_data_state_,           se::Failure,                    end_of_deployment_state_,             tf::Immediate);
44✔
161

162
        main_states_.AddTransition(end_of_deployment_state_,             se::Success,                    idle_state_,                          tf::Immediate);
44✔
163

164
        auto &dt = deployment_tracking_;
44✔
165

166
        dt.states_.AddTransition(dt.idle_state_,                         se::DeploymentStarted,          dt.no_failures_state_,                tf::Immediate);
44✔
167

168
        dt.states_.AddTransition(dt.no_failures_state_,                  se::Failure,                    dt.failure_state_,                    tf::Immediate);
44✔
169
        dt.states_.AddTransition(dt.no_failures_state_,                  se::DeploymentEnded,            dt.idle_state_,                       tf::Immediate);
44✔
170

171
        dt.states_.AddTransition(dt.failure_state_,                      se::RollbackStarted,            dt.rollback_attempted_state_,         tf::Immediate);
44✔
172
        dt.states_.AddTransition(dt.failure_state_,                      se::DeploymentEnded,            dt.idle_state_,                       tf::Immediate);
44✔
173

174
        dt.states_.AddTransition(dt.rollback_attempted_state_,           se::Failure,                    dt.rollback_failed_state_,            tf::Immediate);
44✔
175
        dt.states_.AddTransition(dt.rollback_attempted_state_,           se::DeploymentEnded,            dt.idle_state_,                       tf::Immediate);
44✔
176

177
        dt.states_.AddTransition(dt.rollback_failed_state_,              se::DeploymentEnded,            dt.idle_state_,                       tf::Immediate);
44✔
178
        // clang-format on
179
}
44✔
180

181
StateMachine::StateMachine(
43✔
182
        Context &ctx, events::EventLoop &event_loop, chrono::milliseconds minimum_wait_time) :
43✔
183
        StateMachine(ctx, event_loop) {
43✔
184
        send_commit_status_state_.SetSmallestWaitInterval(minimum_wait_time);
43✔
185
        send_final_status_state_.SetSmallestWaitInterval(minimum_wait_time);
43✔
186
}
43✔
187

188
StateMachine::DeploymentTracking::DeploymentTracking() :
44✔
189
        states_(idle_state_) {
44✔
190
}
44✔
191

192
void StateMachine::LoadStateFromDb() {
43✔
193
        unique_ptr<StateData> state_data(new StateData);
43✔
194
        auto exp_loaded = ctx_.LoadDeploymentStateData(*state_data);
43✔
195
        if (!exp_loaded) {
43✔
196
                if (exp_loaded.error().code
4✔
197
                        == context::MakeError(context::StateDataStoreCountExceededError, "").code) {
4✔
198
                        log::Error("State loop detected. Forcefully aborting update.");
1✔
199

200
                        // This particular error code also fills in state_data.
201
                        ctx_.deployment.state_data = std::move(state_data);
1✔
202

203
                        main_states_.SetState(state_loop_state_);
1✔
204
                        deployment_tracking_.states_.SetState(deployment_tracking_.rollback_failed_state_);
1✔
205
                } else {
206
                        log::Error(
1✔
207
                                "Unable to load deployment data from database: " + exp_loaded.error().String());
2✔
208
                        log::Error("Starting from initial state");
1✔
209
                }
210
                return;
2✔
211
        }
212

213
        if (!exp_loaded.value()) {
41✔
214
                log::Debug("No existing deployment data, starting from initial state");
26✔
215
                return;
26✔
216
        }
217

218
        // We have state data, move it to the context.
219
        ctx_.deployment.state_data = std::move(state_data);
15✔
220

221
        auto &state = ctx_.deployment.state_data->state;
15✔
222

223
        if (state == ctx_.kUpdateStateDownload) {
15✔
224
                main_states_.SetState(update_cleanup_state_);
1✔
225
                // "rollback_attempted_state" because Download in its nature makes no system
226
                // changes, so a rollback is a no-op.
227
                deployment_tracking_.states_.SetState(deployment_tracking_.rollback_attempted_state_);
1✔
228

229
        } else if (state == ctx_.kUpdateStateArtifactReboot) {
14✔
230
                // Normal update path with a reboot.
231
                main_states_.SetState(update_verify_reboot_state_);
2✔
232
                deployment_tracking_.states_.SetState(deployment_tracking_.no_failures_state_);
2✔
233

234
        } else if (state == ctx_.kUpdateStateArtifactRollback) {
12✔
235
                // Installation failed, but rollback could still succeed.
236
                main_states_.SetState(update_rollback_state_);
1✔
237
                deployment_tracking_.states_.SetState(deployment_tracking_.rollback_attempted_state_);
1✔
238

239
        } else if (
11✔
240
                state == ctx_.kUpdateStateArtifactRollbackReboot
11✔
241
                || state == ctx_.kUpdateStateArtifactVerifyRollbackReboot
10✔
242
                || state == ctx_.kUpdateStateVerifyRollbackReboot) {
21✔
243
                // Normal flow for a rebooting rollback.
244
                main_states_.SetState(update_verify_rollback_reboot_state_);
2✔
245
                deployment_tracking_.states_.SetState(deployment_tracking_.rollback_attempted_state_);
2✔
246

247
        } else if (
9✔
248
                state == ctx_.kUpdateStateAfterArtifactCommit
9✔
249
                || state == ctx_.kUpdateStateUpdateAfterFirstCommit) {
9✔
250
                // Re-run commit Leave scripts if spontaneously rebooted after commit.
251
                main_states_.SetState(update_after_commit_state_);
×
252
                deployment_tracking_.states_.SetState(deployment_tracking_.no_failures_state_);
×
253

254
        } else if (state == ctx_.kUpdateStateArtifactFailure) {
9✔
255
                // Re-run ArtifactFailure if spontaneously rebooted before finishing.
256
                main_states_.SetState(update_failure_state_);
2✔
257
                if (ctx_.deployment.state_data->update_info.all_rollbacks_successful) {
2✔
258
                        deployment_tracking_.states_.SetState(deployment_tracking_.rollback_attempted_state_);
1✔
259
                } else {
260
                        deployment_tracking_.states_.SetState(deployment_tracking_.failure_state_);
1✔
261
                }
262

263
        } else if (state == ctx_.kUpdateStateCleanup) {
7✔
264
                // Re-run Cleanup if spontaneously rebooted before finishing.
265
                main_states_.SetState(update_cleanup_state_);
2✔
266
                if (ctx_.deployment.state_data->update_info.all_rollbacks_successful) {
2✔
267
                        deployment_tracking_.states_.SetState(deployment_tracking_.rollback_attempted_state_);
1✔
268
                } else {
269
                        deployment_tracking_.states_.SetState(deployment_tracking_.failure_state_);
1✔
270
                }
271

272
        } else {
273
                // All other states trigger a rollback.
274
                main_states_.SetState(update_check_rollback_state_);
5✔
275
                deployment_tracking_.states_.SetState(deployment_tracking_.failure_state_);
5✔
276
        }
277

278
        auto &payload_types = ctx_.deployment.state_data->update_info.artifact.payload_types;
15✔
279
        assert(payload_types.size() == 1);
15✔
280
        ctx_.deployment.update_module.reset(
15✔
281
                new update_module::UpdateModule(ctx_.mender_context, payload_types[0]));
15✔
282
}
283

284
error::Error StateMachine::Run() {
44✔
285
        // Client is supposed to do one handling of each on startup.
286
        runner_.PostEvent(StateEvent::InventoryPollingTriggered);
44✔
287
        runner_.PostEvent(StateEvent::DeploymentPollingTriggered);
44✔
288

289
        auto err = RegisterSignalHandlers();
88✔
290
        if (err != error::NoError) {
44✔
291
                return err;
×
292
        }
293

294
        event_loop_.Run();
44✔
295
        return exit_state_.exit_error;
44✔
296
}
297

298
void StateMachine::StopAfterDeployment() {
43✔
299
        main_states_.AddTransition(
43✔
300
                end_of_deployment_state_,
301
                StateEvent::DeploymentEnded,
302
                exit_state_,
303
                sm::TransitionFlag::Immediate);
304
}
43✔
305

306
} // namespace daemon
307
} // namespace update
308
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc