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

mendersoftware / mender / 2281564137

23 Jan 2026 10:59AM UTC coverage: 81.48% (+1.7%) from 79.764%
2281564137

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

1 of 1 new or added line in 1 file covered. (100.0%)

327 existing lines in 44 files now uncovered.

8839 of 10848 relevant lines covered (81.48%)

20226.53 hits per line

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

89.34
/src/mender-update/standalone/standalone.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 <mender-update/standalone.hpp>
16

17
#include <common/common.hpp>
18
#include <common/events_io.hpp>
19
#include <common/http.hpp>
20
#include <common/log.hpp>
21
#include <common/path.hpp>
22

23
#include <artifact/v3/scripts/executor.hpp>
24

25
namespace mender {
26
namespace update {
27
namespace standalone {
28

29
using namespace std;
30

31
namespace common = mender::common;
32
namespace events = mender::common::events;
33
namespace executor = mender::artifact::scripts::executor;
34
namespace http = mender::common::http;
35
namespace io = mender::common::io;
36
namespace log = mender::common::log;
37
namespace path = mender::common::path;
38

39
ExpectedOptionalStateData LoadStateData(database::KeyValueDatabase &db) {
90✔
40
        StateDataKeys keys;
41
        StateData dst;
90✔
42

43
        auto exp_bytes = db.Read(context::MenderContext::standalone_state_key);
90✔
44
        if (!exp_bytes) {
90✔
45
                auto &err = exp_bytes.error();
46
                if (err.code == database::MakeError(database::KeyError, "").code) {
128✔
47
                        return optional<StateData>();
128✔
48
                } else {
49
                        return expected::unexpected(err);
×
50
                }
51
        }
52

53
        auto exp_json = json::Load(common::StringFromByteVector(exp_bytes.value()));
52✔
54
        if (!exp_json) {
26✔
55
                return expected::unexpected(exp_json.error());
×
56
        }
57
        auto &json = exp_json.value();
26✔
58

59
        auto exp_int = json::Get<int>(json, keys.version, json::MissingOk::No);
26✔
60
        if (!exp_int) {
26✔
61
                return expected::unexpected(exp_int.error());
×
62
        }
63
        dst.version = exp_int.value();
26✔
64

65
        if (dst.version != 1 && dst.version != context::MenderContext::standalone_data_version) {
26✔
66
                return expected::unexpected(error::Error(
×
67
                        make_error_condition(errc::not_supported),
×
68
                        "State data has a version which is not supported by this client"));
×
69
        }
70

71
        auto exp_string = json::Get<string>(json, keys.artifact_name, json::MissingOk::No);
26✔
72
        if (!exp_string) {
26✔
73
                return expected::unexpected(exp_string.error());
×
74
        }
75
        dst.artifact_name = exp_string.value();
26✔
76

77
        exp_string = json::Get<string>(json, keys.artifact_group, json::MissingOk::Yes);
52✔
78
        if (!exp_string) {
26✔
79
                return expected::unexpected(exp_string.error());
×
80
        }
81
        dst.artifact_group = exp_string.value();
26✔
82

83
        auto exp_map = json::Get<json::KeyValueMap>(json, keys.artifact_provides, json::MissingOk::No);
26✔
84
        if (exp_map) {
26✔
85
                dst.artifact_provides = exp_map.value();
25✔
86
        } else {
87
                dst.artifact_provides.reset();
88
        }
89

90
        auto exp_array =
91
                json::Get<vector<string>>(json, keys.artifact_clears_provides, json::MissingOk::No);
26✔
92
        if (exp_array) {
26✔
93
                dst.artifact_clears_provides = exp_array.value();
25✔
94
        } else {
95
                dst.artifact_clears_provides.reset();
96
        }
97

98
        exp_array = json::Get<vector<string>>(json, keys.payload_types, json::MissingOk::No);
52✔
99
        if (!exp_array) {
26✔
100
                return expected::unexpected(exp_array.error());
×
101
        }
102
        dst.payload_types = exp_array.value();
26✔
103

104
        if (dst.version == 1) {
26✔
105
                // In version 1, if there is any data at all, it is equivalent to this:
106
                dst.in_state = StateData::kBeforeStateArtifactCommit_Enter;
107
                dst.failed = false;
×
108
                dst.rolled_back = false;
×
109

110
                // Additionally, there is never any situation where we want to save version 1 data,
111
                // because it only has one state: The one we just loaded in the previous
112
                // statement. In a rollback situation, all states are always carried out and the
113
                // data is removed instead. Therefore, always set it to version 2, so we can't even
114
                // theoretically save it wrongly (and we don't need to handle it in the saving
115
                // code).
116
                dst.version = context::MenderContext::standalone_data_version;
×
117
        } else {
118
                exp_string = json::Get<string>(json, keys.in_state, json::MissingOk::No);
52✔
119
                if (!exp_string) {
26✔
120
                        return expected::unexpected(exp_string.error());
×
121
                }
122
                dst.in_state = exp_string.value();
26✔
123

124
                auto exp_bool = json::Get<bool>(json, keys.failed, json::MissingOk::No);
26✔
125
                if (!exp_bool) {
26✔
126
                        return expected::unexpected(exp_bool.error());
×
127
                }
128
                dst.failed = exp_bool.value();
26✔
129

130
                exp_bool = json::Get<bool>(json, keys.rolled_back, json::MissingOk::No);
52✔
131
                if (!exp_bool) {
26✔
132
                        return expected::unexpected(exp_bool.error());
×
133
                }
134
                dst.rolled_back = exp_bool.value();
26✔
135
        }
136

137
        if (dst.artifact_name == "") {
26✔
138
                return expected::unexpected(context::MakeError(
×
139
                        context::DatabaseValueError, "`" + keys.artifact_name + "` is empty"));
×
140
        }
141

142
        if (dst.payload_types.size() == 0) {
26✔
143
                return expected::unexpected(context::MakeError(
×
144
                        context::DatabaseValueError, "`" + keys.payload_types + "` is empty"));
×
145
        }
146
        if (dst.payload_types.size() >= 2) {
26✔
147
                return expected::unexpected(error::Error(
×
148
                        make_error_condition(errc::not_supported),
×
149
                        "`" + keys.payload_types + "` contains multiple payloads"));
×
150
        }
151

152
        return dst;
26✔
153
}
90✔
154

155
error::Error SaveStateData(database::KeyValueDatabase &db, const StateData &data) {
386✔
156
        return db.WriteTransaction(
772✔
157
                [&data](database::Transaction &txn) { return SaveStateData(txn, data); });
1,158✔
158
}
159

160
error::Error SaveStateData(database::Transaction &txn, const StateData &data) {
387✔
161
        StateDataKeys keys;
162
        stringstream ss;
387✔
163
        ss << "{";
387✔
164
        ss << "\"" << keys.version << "\":" << data.version;
387✔
165

166
        ss << ",";
387✔
167
        ss << "\"" << keys.artifact_name << "\":\"" << json::EscapeString(data.artifact_name) << "\"";
387✔
168

169
        ss << ",";
387✔
170
        ss << "\"" << keys.artifact_group << "\":\"" << json::EscapeString(data.artifact_group) << "\"";
387✔
171

172
        ss << ",";
387✔
173
        ss << "\"" << keys.payload_types << "\":[";
387✔
174
        bool first = true;
175
        for (auto elem : data.payload_types) {
774✔
176
                if (!first) {
387✔
177
                        ss << ",";
×
178
                }
179
                ss << "\"" << json::EscapeString(elem) << "\"";
774✔
180
                first = false;
181
        }
182
        ss << "]";
387✔
183

184
        if (data.artifact_provides) {
387✔
185
                ss << ",";
379✔
186
                ss << "\"" << keys.artifact_provides << "\":{";
379✔
187
                bool first = true;
188
                for (auto elem : data.artifact_provides.value()) {
1,137✔
189
                        if (!first) {
758✔
190
                                ss << ",";
379✔
191
                        }
192
                        ss << "\"" << json::EscapeString(elem.first) << "\":\""
758✔
193
                           << json::EscapeString(elem.second) << "\"";
2,274✔
194
                        first = false;
195
                }
758✔
196
                ss << "}";
379✔
197
        }
198

199
        if (data.artifact_clears_provides) {
387✔
200
                ss << ",";
379✔
201
                ss << "\"" << keys.artifact_clears_provides << "\":[";
379✔
202
                bool first = true;
203
                for (auto elem : data.artifact_clears_provides.value()) {
1,516✔
204
                        if (!first) {
1,137✔
205
                                ss << ",";
758✔
206
                        }
207
                        ss << "\"" << json::EscapeString(elem) << "\"";
2,274✔
208
                        first = false;
209
                }
210
                ss << "]";
379✔
211
        }
212

213
        ss << R"(,")" << keys.in_state << R"(":")" << data.in_state << R"(")";
387✔
214

215
        ss << R"(,")" << keys.failed << R"(":)" << (data.failed ? "true" : "false");
739✔
216

217
        ss << R"(,")" << keys.rolled_back << R"(":)" << (data.rolled_back ? "true" : "false");
752✔
218

219
        ss << "}";
387✔
220

221
        string strdata = ss.str();
222
        vector<uint8_t> bytedata(common::ByteVectorFromString(strdata));
387✔
223

224
        return txn.Write(context::MenderContext::standalone_state_key, bytedata);
774✔
225
}
387✔
226

227
StateMachine::StateMachine(Context &ctx) :
87✔
228
        context_ {ctx},
87✔
229
        download_enter_state_ {
230
                executor::State::Download,
231
                executor::Action::Enter,
232
                executor::OnError::Fail,
233
                Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary},
234
        download_leave_state_ {
235
                executor::State::Download,
236
                executor::Action::Leave,
237
                executor::OnError::Fail,
238
                Result::DownloadFailed | Result::Failed | Result::NoRollbackNecessary},
239
        download_error_state_ {
240
                executor::State::Download,
241
                executor::Action::Error,
242
                executor::OnError::Ignore,
243
                Result::NoResult},
244
        save_before_artifact_install_state_ {StateData::kBeforeStateArtifactInstall_Enter},
87✔
245
        save_artifact_install_state_ {StateData::kInStateArtifactInstall_Enter},
87✔
246
        artifact_install_enter_state_ {
247
                executor::State::ArtifactInstall,
248
                executor::Action::Enter,
249
                executor::OnError::Fail,
250
                Result::InstallFailed | Result::Failed},
251
        artifact_install_leave_state_ {
252
                executor::State::ArtifactInstall,
253
                executor::Action::Leave,
254
                executor::OnError::Fail,
255
                Result::InstallFailed | Result::Failed},
256
        artifact_install_error_state_ {
257
                executor::State::ArtifactInstall,
258
                executor::Action::Error,
259
                executor::OnError::Ignore,
260
                Result::NoResult},
261
        save_before_artifact_commit_state_ {StateData::kBeforeStateArtifactCommit_Enter},
87✔
262
        save_artifact_commit_state_ {StateData::kInStateArtifactCommit_Enter},
87✔
263
        artifact_commit_enter_state_ {
264
                executor::State::ArtifactCommit,
265
                executor::Action::Enter,
266
                executor::OnError::Fail,
267
                Result::CommitFailed | Result::Failed},
268
        save_before_artifact_commit_leave_state_ {StateData::kBeforeStateArtifactCommit_Leave},
87✔
269
        save_artifact_commit_leave_state_ {StateData::kInStateArtifactCommit_Leave},
87✔
270
        artifact_commit_leave_state_ {
271
                executor::State::ArtifactCommit,
272
                executor::Action::Leave,
273
                executor::OnError::Ignore,
274
                Result::CommitFailed | Result::Failed | Result::FailedInPostCommit},
275
        artifact_commit_error_state_ {
276
                executor::State::ArtifactCommit,
277
                executor::Action::Error,
278
                executor::OnError::Ignore,
279
                Result::NoResult},
280
        save_artifact_rollback_state_ {StateData::kInStateArtifactRollback_Enter},
87✔
281
        artifact_rollback_enter_state_ {
282
                executor::State::ArtifactRollback,
283
                executor::Action::Enter,
284
                executor::OnError::Ignore,
285
                Result::Failed | Result::RollbackFailed},
286
        artifact_rollback_leave_state_ {
287
                executor::State::ArtifactRollback,
288
                executor::Action::Leave,
289
                executor::OnError::Ignore,
290
                Result::NoResult},
291
        save_artifact_failure_state_ {StateData::kInStateArtifactFailure_Enter},
87✔
292
        artifact_failure_enter_state_ {
293
                executor::State::ArtifactFailure,
294
                executor::Action::Enter,
295
                executor::OnError::Ignore,
296
                Result::Failed | Result::RollbackFailed},
297
        artifact_failure_leave_state_ {
298
                executor::State::ArtifactFailure,
299
                executor::Action::Leave,
300
                executor::OnError::Ignore,
301
                Result::NoResult},
302
        save_cleanup_state_ {StateData::kInStateCleanup},
87✔
303
        exit_state_ {loop_},
87✔
304
        start_state_ {&prepare_download_state_},
87✔
305
        state_machine_ {*start_state_} {
174✔
306
        using tf = common::state_machine::TransitionFlag;
307
        using se = StateEvent;
308
        auto &s = state_machine_;
309

310
        // clang-format off
311
        s.AddTransition(prepare_download_state_,                  se::Success,              download_enter_state_,                    tf::Immediate);
87✔
312
        s.AddTransition(prepare_download_state_,                  se::Failure,              exit_state_,                              tf::Immediate);
87✔
313
        s.AddTransition(prepare_download_state_,                  se::EmptyPayloadArtifact, exit_state_,                              tf::Immediate);
87✔
314

315
        s.AddTransition(download_enter_state_,                    se::Success,              download_state_,                          tf::Immediate);
87✔
316
        s.AddTransition(download_enter_state_,                    se::Failure,              download_error_state_,                    tf::Immediate);
87✔
317

318
        s.AddTransition(download_state_,                          se::Success,              download_leave_state_,                    tf::Immediate);
87✔
319
        s.AddTransition(download_state_,                          se::Failure,              download_error_state_,                    tf::Immediate);
87✔
320

321
        s.AddTransition(download_leave_state_,                    se::Success,              save_before_artifact_install_state_,      tf::Immediate);
87✔
322
        s.AddTransition(download_leave_state_,                    se::Failure,              download_error_state_,                    tf::Immediate);
87✔
323

324
        s.AddTransition(download_error_state_,                    se::Success,              save_cleanup_state_,                      tf::Immediate);
87✔
325
        s.AddTransition(download_error_state_,                    se::Failure,              save_cleanup_state_,                      tf::Immediate);
87✔
326

327
        // The reason we have a "save_before" state followed by a "save" state is the
328
        // `--stop-before` argument. We want to make sure that:
329

330
        // 1. If you specify the flag twice in a row, the second run is a noop (just stops at the
331
        //    same point). This is accomplished using the "save_before" state, which we return to
332
        //    during a DB recovery.
333

334
        // 2. If we have started executing the following states, it should no longer we possible to
335
        //    use the `--stop-before` flag for that state, since the state execution has started
336
        //    already. This is done by saving a different value in the "save" state, and is thus
337
        //    preserved even after a spontaneous reboot. Once we have gone there, there is no going
338
        //    back.
339
        s.AddTransition(save_before_artifact_install_state_,      se::Success,              save_artifact_install_state_,             tf::Immediate);
87✔
340
        s.AddTransition(save_before_artifact_install_state_,      se::Failure,              save_cleanup_state_,                      tf::Immediate);
87✔
341

342
        s.AddTransition(save_artifact_install_state_,             se::Success,              artifact_install_enter_state_,            tf::Immediate);
87✔
343
        s.AddTransition(save_artifact_install_state_,             se::Failure,              save_cleanup_state_,                      tf::Immediate);
87✔
344

345
        s.AddTransition(artifact_install_enter_state_,            se::Success,              artifact_install_state_,                  tf::Immediate);
87✔
346
        s.AddTransition(artifact_install_enter_state_,            se::Failure,              artifact_install_error_state_,            tf::Immediate);
87✔
347

348
        s.AddTransition(artifact_install_state_,                  se::Success,              artifact_install_leave_state_,            tf::Immediate);
87✔
349
        s.AddTransition(artifact_install_state_,                  se::Failure,              artifact_install_error_state_,            tf::Immediate);
87✔
350

351
        s.AddTransition(artifact_install_leave_state_,            se::Success,              reboot_and_rollback_query_state_,         tf::Immediate);
87✔
352
        s.AddTransition(artifact_install_leave_state_,            se::Failure,              artifact_install_error_state_,            tf::Immediate);
87✔
353

354
        s.AddTransition(artifact_install_error_state_,            se::Success,              rollback_query_state_,                    tf::Immediate);
87✔
355
        s.AddTransition(artifact_install_error_state_,            se::Failure,              rollback_query_state_,                    tf::Immediate);
87✔
356

357
        s.AddTransition(reboot_and_rollback_query_state_,         se::Success,              save_before_artifact_commit_state_,       tf::Immediate);
87✔
358
        s.AddTransition(reboot_and_rollback_query_state_,         se::Failure,              rollback_query_state_,                    tf::Immediate);
87✔
359
        s.AddTransition(reboot_and_rollback_query_state_,         se::NeedsInteraction,     exit_state_,                              tf::Immediate);
87✔
360

361
        // See `save_before_artifact_install_state_` for an explanation of the following two states.
362
        s.AddTransition(save_before_artifact_commit_state_,       se::Success,              save_artifact_commit_state_,              tf::Immediate);
87✔
363
        s.AddTransition(save_before_artifact_commit_state_,       se::Failure,              rollback_query_state_,                    tf::Immediate);
87✔
364

365
        s.AddTransition(save_artifact_commit_state_,              se::Success,              artifact_commit_enter_state_,             tf::Immediate);
87✔
366
        s.AddTransition(save_artifact_commit_state_,              se::Failure,              rollback_query_state_,                    tf::Immediate);
87✔
367

368
        s.AddTransition(artifact_commit_enter_state_,             se::Success,              artifact_commit_state_,                   tf::Immediate);
87✔
369
        s.AddTransition(artifact_commit_enter_state_,             se::Failure,              artifact_commit_error_state_,             tf::Immediate);
87✔
370

371
        s.AddTransition(artifact_commit_state_,                   se::Success,              save_before_artifact_commit_leave_state_, tf::Immediate);
87✔
372
        s.AddTransition(artifact_commit_state_,                   se::Failure,              artifact_commit_error_state_,             tf::Immediate);
87✔
373

374
        // See `save_before_artifact_install_state_` for an explanation of the following two states.
375
        s.AddTransition(save_before_artifact_commit_leave_state_, se::Success,              save_artifact_commit_leave_state_,        tf::Immediate);
87✔
376
        s.AddTransition(save_before_artifact_commit_leave_state_, se::Failure,              artifact_commit_error_state_,             tf::Immediate);
87✔
377

378
        s.AddTransition(save_artifact_commit_leave_state_,        se::Success,              artifact_commit_leave_state_,             tf::Immediate);
87✔
379
        s.AddTransition(save_artifact_commit_leave_state_,        se::Failure,              artifact_commit_error_state_,             tf::Immediate);
87✔
380

381
        s.AddTransition(artifact_commit_leave_state_,             se::Success,              save_cleanup_state_,                      tf::Immediate);
87✔
382
        s.AddTransition(artifact_commit_leave_state_,             se::Failure,              save_cleanup_state_,                      tf::Immediate);
87✔
383

384
        s.AddTransition(rollback_query_state_,                    se::Success,              save_artifact_rollback_state_,            tf::Immediate);
87✔
385
        s.AddTransition(rollback_query_state_,                    se::NothingToDo,          save_artifact_failure_state_,             tf::Immediate);
87✔
386
        s.AddTransition(rollback_query_state_,                    se::Failure,              save_artifact_failure_state_,             tf::Immediate);
87✔
387
        s.AddTransition(rollback_query_state_,                    se::NeedsInteraction,     exit_state_,                              tf::Immediate);
87✔
388

389
        s.AddTransition(artifact_commit_error_state_,             se::Success,              rollback_query_state_,                    tf::Immediate);
87✔
390
        s.AddTransition(artifact_commit_error_state_,             se::Failure,              rollback_query_state_,                    tf::Immediate);
87✔
391

392
        // Note: States on the error path are supposed to be idempotent, and may execute several
393
        // times if interrupted by a powerloss. Hence they don't need `save_before` states. See
394
        // `save_before_artifact_install_state_` for more context.
395
        s.AddTransition(save_artifact_rollback_state_,            se::Success,              artifact_rollback_enter_state_,           tf::Immediate);
87✔
396
        s.AddTransition(save_artifact_rollback_state_,            se::Failure,              artifact_rollback_enter_state_,           tf::Immediate);
87✔
397

398
        s.AddTransition(artifact_rollback_enter_state_,           se::Success,              artifact_rollback_state_,                 tf::Immediate);
87✔
399
        s.AddTransition(artifact_rollback_enter_state_,           se::Failure,              artifact_rollback_state_,                 tf::Immediate);
87✔
400

401
        s.AddTransition(artifact_rollback_state_,                 se::Success,              artifact_rollback_leave_state_,           tf::Immediate);
87✔
402
        s.AddTransition(artifact_rollback_state_,                 se::Failure,              artifact_rollback_leave_state_,           tf::Immediate);
87✔
403

404
        s.AddTransition(artifact_rollback_leave_state_,           se::Success,              save_artifact_failure_state_,             tf::Immediate);
87✔
405
        s.AddTransition(artifact_rollback_leave_state_,           se::Failure,              save_artifact_failure_state_,             tf::Immediate);
87✔
406

407
        // See comment for `save_artifact_rollback_state_`.
408
        s.AddTransition(save_artifact_failure_state_,             se::Success,              artifact_failure_enter_state_,            tf::Immediate);
87✔
409
        s.AddTransition(save_artifact_failure_state_,             se::Failure,              artifact_failure_enter_state_,            tf::Immediate);
87✔
410

411
        s.AddTransition(artifact_failure_enter_state_,            se::Success,              artifact_failure_state_,                  tf::Immediate);
87✔
412
        s.AddTransition(artifact_failure_enter_state_,            se::Failure,              artifact_failure_state_,                  tf::Immediate);
87✔
413

414
        s.AddTransition(artifact_failure_state_,                  se::Success,              artifact_failure_leave_state_,            tf::Immediate);
87✔
415
        s.AddTransition(artifact_failure_state_,                  se::Failure,              artifact_failure_leave_state_,            tf::Immediate);
87✔
416

417
        s.AddTransition(artifact_failure_leave_state_,            se::Success,              save_cleanup_state_,                      tf::Immediate);
87✔
418
        s.AddTransition(artifact_failure_leave_state_,            se::Failure,              save_cleanup_state_,                      tf::Immediate);
87✔
419

420
        // See comment for `save_artifact_rollback_state_`. While cleanup is not strictly an error
421
        // state, it is idempotent.
422
        s.AddTransition(save_cleanup_state_,                      se::Success,              cleanup_state_,                           tf::Immediate);
87✔
423
        s.AddTransition(save_cleanup_state_,                      se::Failure,              cleanup_state_,                           tf::Immediate);
87✔
424

425
        s.AddTransition(cleanup_state_,                           se::Success,              exit_state_,                              tf::Immediate);
87✔
426
        s.AddTransition(cleanup_state_,                           se::Failure,              exit_state_,                              tf::Immediate);
87✔
427
        // clang-format on
428
}
87✔
429

430
void StateMachine::Run() {
87✔
431
        common::state_machine::StateMachineRunner<Context, StateEvent> runner {context_};
87✔
432
        runner.AddStateMachine(state_machine_);
87✔
433
        runner.AttachToEventLoop(loop_);
87✔
434

435
        state_machine_.SetState(*start_state_);
87✔
436

437
        loop_.Run();
87✔
438
}
87✔
439

440
error::Error StateMachine::SetStartStateFromStateData(const string &in_state) {
18✔
441
        if (in_state == StateData::kBeforeStateArtifactInstall_Enter) {
18✔
442
                start_state_ = &save_before_artifact_install_state_;
2✔
443
        } else if (in_state == StateData::kInStateArtifactInstall_Enter) {
16✔
444
                start_state_ = &save_artifact_rollback_state_;
×
445
        } else if (in_state == StateData::kBeforeStateArtifactCommit_Enter) {
16✔
446
                start_state_ = &save_before_artifact_commit_state_;
8✔
447
        } else if (in_state == StateData::kInStateArtifactCommit_Enter) {
8✔
448
                start_state_ = &save_artifact_rollback_state_;
×
449
        } else if (in_state == StateData::kBeforeStateArtifactCommit_Leave) {
8✔
450
                start_state_ = &save_before_artifact_commit_leave_state_;
3✔
451
        } else if (in_state == StateData::kInStateArtifactCommit_Leave) {
5✔
452
                start_state_ = &artifact_commit_leave_state_;
×
453
        } else if (in_state == StateData::kInStateCleanup) {
5✔
454
                start_state_ = &save_cleanup_state_;
2✔
455
        } else if (in_state == StateData::kInStateArtifactRollback_Enter) {
3✔
456
                start_state_ = &save_artifact_rollback_state_;
1✔
457
        } else if (in_state == StateData::kInStateArtifactFailure_Enter) {
2✔
458
                start_state_ = &save_artifact_failure_state_;
2✔
459
        } else {
460
                return context::MakeError(
461
                        context::DatabaseValueError, "Invalid InState in database " + in_state);
×
462
        }
463

464
        return error::NoError;
18✔
465
}
466

467
error::Error StateMachine::AddStopBeforeState(const string &state) {
16✔
468
        using tf = common::state_machine::TransitionFlag;
469
        using se = StateEvent;
470
        auto &s = state_machine_;
16✔
471

472
        // Replace transition in state machine in order to exit at given point.
473
        if (state == "ArtifactInstall_Enter") {
16✔
474
                s.AddTransition(
2✔
475
                        save_before_artifact_install_state_, se::Success, exit_state_, tf::Immediate);
476

477
        } else if (state == "ArtifactCommit_Enter") {
14✔
478
                s.AddTransition(
4✔
479
                        save_before_artifact_commit_state_, se::Success, exit_state_, tf::Immediate);
480

481
        } else if (state == "ArtifactCommit_Leave") {
10✔
482
                s.AddTransition(
4✔
483
                        save_before_artifact_commit_leave_state_, se::Success, exit_state_, tf::Immediate);
484

485
        } else if (state == "ArtifactRollback_Enter") {
6✔
486
                s.AddTransition(save_artifact_rollback_state_, se::Success, exit_state_, tf::Immediate);
2✔
487
                s.AddTransition(save_artifact_rollback_state_, se::Failure, exit_state_, tf::Immediate);
2✔
488

489
        } else if (state == "ArtifactFailure_Enter") {
4✔
490
                s.AddTransition(save_artifact_failure_state_, se::Success, exit_state_, tf::Immediate);
2✔
491
                s.AddTransition(save_artifact_failure_state_, se::Failure, exit_state_, tf::Immediate);
2✔
492

493
        } else if (state == "Cleanup") {
2✔
494
                s.AddTransition(save_cleanup_state_, se::Success, exit_state_, tf::Immediate);
2✔
495
                s.AddTransition(save_cleanup_state_, se::Failure, exit_state_, tf::Immediate);
2✔
496

497
        } else if (state != "") {
×
498
                return context::MakeError(
499
                        context::ValueError, "Cannot stop before unsupported state " + state);
×
500
        }
501

502
        return error::NoError;
16✔
503
}
504

505
void StateMachine::StartOnRollback() {
×
506
        start_state_ = &rollback_query_state_;
7✔
507
}
×
508

509
error::Error PrepareContext(Context &ctx) {
87✔
510
        const auto &conf = ctx.main_context.GetConfig();
87✔
511
        const auto &paths {conf.paths};
512
        ctx.script_runner.reset(new executor::ScriptRunner(
513
                ctx.loop,
514
                chrono::seconds {conf.state_script_timeout_seconds},
515
                chrono::seconds {conf.state_script_retry_interval_seconds},
516
                chrono::seconds {conf.state_script_retry_timeout_seconds},
517
                paths.GetArtScriptsPath(),
87✔
518
                paths.GetRootfsScriptsPath()));
435✔
519

520
        return error::NoError;
87✔
521
}
522

523
error::Error PrepareContextFromStateData(Context &ctx, const StateData &data) {
25✔
524
        auto exp_update_module =
525
                update_module::UpdateModule::Create(ctx.main_context, data.payload_types[0]);
25✔
526
        if (!exp_update_module.has_value()) {
25✔
527
                return exp_update_module.error();
×
528
        }
529
        ctx.update_module = std::move(exp_update_module.value());
25✔
530

531
        if (data.payload_types[0] == "rootfs-image") {
25✔
532
                // Special case for rootfs-image upgrades. See comments inside the function.
533
                auto err = ctx.update_module->EnsureRootfsImageFileTree(
534
                        ctx.update_module->GetUpdateModuleWorkDir());
25✔
535
                if (err != error::NoError) {
25✔
536
                        return err;
×
537
                }
538
        }
539

540
        if (data.failed) {
25✔
541
                ctx.result_and_error.result = ctx.result_and_error.result | Result::Failed;
×
542
        }
543

544
        if (data.rolled_back) {
25✔
545
                ctx.result_and_error.result = ctx.result_and_error.result | Result::RolledBack;
4✔
546
        }
547

548
        return error::NoError;
25✔
549
}
550

551
ResultAndError Install(
63✔
552
        Context &ctx,
553
        const string &src,
554
        const artifact::config::Signature verify_signature,
555
        InstallOptions options) {
556
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
63✔
557
        if (!exp_in_progress) {
63✔
558
                return {Result::Failed, exp_in_progress.error()};
559
        }
560
        auto &in_progress = exp_in_progress.value();
63✔
561

562
        if (in_progress) {
63✔
563
                return {
564
                        Result::Failed | Result::NoRollbackNecessary,
565
                        error::Error(
566
                                make_error_condition(errc::operation_in_progress),
2✔
567
                                "Update already in progress. Please commit or roll back first")};
1✔
568
        }
569

570
        auto err = PrepareContext(ctx);
62✔
571
        if (err != error::NoError) {
62✔
572
                return {Result::Failed, err};
573
        }
574

575
        ctx.artifact_src = src;
62✔
576
        ctx.verify_signature = verify_signature;
62✔
577
        ctx.options = options;
62✔
578

579
        StateMachine state_machine {ctx};
62✔
580

581
        for (const auto &state : ctx.stop_before) {
67✔
582
                err = state_machine.AddStopBeforeState(state);
5✔
583
                if (err != error::NoError) {
5✔
584
                        return {Result::Failed, err};
585
                }
586
        }
587

588
        state_machine.Run();
62✔
589

590
        return ctx.result_and_error;
62✔
591
}
63✔
592

593
ResultAndError Resume(Context &ctx) {
13✔
594
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
13✔
595
        if (!exp_in_progress) {
13✔
596
                return {Result::Failed, exp_in_progress.error()};
597
        }
598
        auto &in_progress = exp_in_progress.value();
13✔
599

600
        if (!in_progress) {
13✔
601
                return {
602
                        Result::NoUpdateInProgress,
603
                        context::MakeError(context::NoUpdateInProgressError, "Cannot resume")};
×
604
        }
605
        ctx.state_data = in_progress.value();
13✔
606

607
        auto err = PrepareContext(ctx);
13✔
608
        if (err != error::NoError) {
13✔
609
                return {Result::Failed, err};
610
        }
611

612
        err = PrepareContextFromStateData(ctx, ctx.state_data);
13✔
613
        if (err != error::NoError) {
13✔
614
                return {Result::Failed, err};
615
        }
616

617
        StateMachine state_machine {ctx};
13✔
618

619
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
13✔
620
        if (err != error::NoError) {
13✔
621
                return {Result::Failed, err};
622
        }
623

624
        for (const auto &state : ctx.stop_before) {
21✔
625
                err = state_machine.AddStopBeforeState(state);
8✔
626
                if (err != error::NoError) {
8✔
627
                        return {Result::Failed, err};
628
                }
629
        }
630

631
        state_machine.Run();
13✔
632

633
        return ctx.result_and_error;
13✔
634
}
13✔
635

636
ResultAndError Commit(Context &ctx) {
6✔
637
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
6✔
638
        if (!exp_in_progress) {
6✔
639
                return {Result::Failed, exp_in_progress.error()};
640
        }
641
        auto &in_progress = exp_in_progress.value();
6✔
642

643
        if (!in_progress) {
6✔
644
                return {
645
                        Result::NoUpdateInProgress,
646
                        context::MakeError(context::NoUpdateInProgressError, "Cannot commit")};
1✔
647
        }
648
        ctx.state_data = in_progress.value();
5✔
649

650
        if (ctx.state_data.in_state != StateData::kBeforeStateArtifactCommit_Enter
5✔
651
                and ctx.state_data.in_state != StateData::kInStateArtifactCommit_Enter) {
5✔
652
                return {
653
                        Result::Failed,
UNCOV
654
                        context::MakeError(
×
655
                                context::WrongOperationError,
656
                                "Cannot commit from this state. "
657
                                "Make sure that the `install` command has run successfully and the device is expecting a commit.")};
658
        }
659

660
        auto err = PrepareContext(ctx);
5✔
661
        if (err != error::NoError) {
5✔
662
                return {Result::Failed, err};
663
        }
664

665
        err = PrepareContextFromStateData(ctx, ctx.state_data);
5✔
666
        if (err != error::NoError) {
5✔
667
                return {Result::Failed, err};
668
        }
669

670
        StateMachine state_machine {ctx};
5✔
671

672
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
5✔
673
        if (err != error::NoError) {
5✔
674
                return {Result::Failed, err};
675
        }
676

677
        for (const auto &state : ctx.stop_before) {
7✔
678
                err = state_machine.AddStopBeforeState(state);
2✔
679
                if (err != error::NoError) {
2✔
680
                        return {Result::Failed, err};
681
                }
682
        }
683

684
        state_machine.Run();
5✔
685

686
        return ctx.result_and_error;
5✔
687
}
6✔
688

689
ResultAndError Rollback(Context &ctx) {
8✔
690
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
8✔
691
        if (!exp_in_progress) {
8✔
692
                return {Result::Failed, exp_in_progress.error()};
693
        }
694
        auto &in_progress = exp_in_progress.value();
8✔
695

696
        if (!in_progress) {
8✔
697
                return {
698
                        Result::NoUpdateInProgress,
699
                        context::MakeError(context::NoUpdateInProgressError, "Cannot roll back")};
1✔
700
        }
701
        ctx.state_data = in_progress.value();
7✔
702

703
        if (ctx.state_data.in_state != StateData::kBeforeStateArtifactInstall_Enter
7✔
704
                and ctx.state_data.in_state != StateData::kInStateArtifactInstall_Enter
7✔
705
                and ctx.state_data.in_state != StateData::kBeforeStateArtifactCommit_Enter
7✔
706
                and ctx.state_data.in_state != StateData::kInStateArtifactCommit_Enter
2✔
707
                and ctx.state_data.in_state != StateData::kBeforeStateArtifactCommit_Leave
2✔
708
                and ctx.state_data.in_state != StateData::kInStateArtifactRollback_Enter) {
8✔
709
                return {
710
                        Result::Failed,
UNCOV
711
                        context::MakeError(
×
712
                                context::WrongOperationError,
713
                                "Cannot roll back from this state. "
714
                                "Use `resume` to complete the current install in order to start a new one.")};
715
        }
716

717
        auto err = PrepareContext(ctx);
7✔
718
        if (err != error::NoError) {
7✔
719
                return {Result::Failed, err};
720
        }
721

722
        err = PrepareContextFromStateData(ctx, ctx.state_data);
7✔
723
        if (err != error::NoError) {
7✔
724
                return {Result::Failed, err};
725
        }
726

727
        StateMachine state_machine {ctx};
7✔
728
        state_machine.StartOnRollback();
729
        if (err != error::NoError) {
7✔
730
                return {Result::Failed, err};
731
        }
732

733
        for (const auto &state : ctx.stop_before) {
8✔
734
                err = state_machine.AddStopBeforeState(state);
1✔
735
                if (err != error::NoError) {
1✔
736
                        return {Result::Failed, err};
737
                }
738
        }
739

740
        state_machine.Run();
7✔
741

742
        return ctx.result_and_error;
7✔
743
}
8✔
744

745
} // namespace standalone
746
} // namespace update
747
} // 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

© 2026 Coveralls, Inc