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

mendersoftware / mender / 1487964749

09 Oct 2024 09:17AM UTC coverage: 76.332% (+0.05%) from 76.281%
1487964749

push

gitlab-ci

kacf
chore: Increase robustness of `--stop-before flag.

First and foremost, make sure that repeated resuming before the same
state, with the same `--stop-before` flag, is a noop. It should keep
stopping there. Also make sure that once we have started executing the
path after that, it should no longer be possible to go back.

For this we introduce some new DB flags, but since the existing ones
have not been released yet, this should be fine.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

94 of 96 new or added lines in 2 files covered. (97.92%)

170 existing lines in 4 files now uncovered.

7308 of 9574 relevant lines covered (76.33%)

11298.84 hits per line

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

84.21
/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;
180✔
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) {
64✔
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<int64_t>(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::kInStateArtifactCommit_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
}
154

155
error::Error SaveStateData(database::KeyValueDatabase &db, const StateData &data) {
386✔
156
        return db.WriteTransaction(
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;
774✔
163
        ss << "{";
387✔
164
        ss << "\"" << keys.version << "\":" << data.version;
387✔
165

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

169
        ss << ",";
387✔
170
        ss << "\"" << keys.artifact_group << "\":\"" << 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 << "\"" << elem << "\"";
387✔
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()) {
2,274✔
189
                        if (!first) {
758✔
190
                                ss << ",";
379✔
191
                        }
192
                        ss << "\"" << elem.first << "\":\"" << elem.second << "\"";
758✔
193
                        first = false;
194
                }
195
                ss << "}";
379✔
196
        }
197

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

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

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

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

218
        ss << "}";
387✔
219

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

522
error::Error PrepareContextFromStateData(Context &ctx, const StateData &data) {
25✔
523
        ctx.update_module.reset(
524
                new update_module::UpdateModule(ctx.main_context, data.payload_types[0]));
25✔
525

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

535
        if (data.failed) {
25✔
UNCOV
536
                ctx.result_and_error.result = ctx.result_and_error.result | Result::Failed;
×
537
        }
538

539
        if (data.rolled_back) {
25✔
540
                ctx.result_and_error.result = ctx.result_and_error.result | Result::RolledBack;
4✔
541
        }
542

543
        return error::NoError;
25✔
544
}
545

546
ResultAndError Install(
63✔
547
        Context &ctx,
548
        const string &src,
549
        const artifact::config::Signature verify_signature,
550
        InstallOptions options) {
551
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
63✔
552
        if (!exp_in_progress) {
63✔
UNCOV
553
                return {Result::Failed, exp_in_progress.error()};
×
554
        }
555
        auto &in_progress = exp_in_progress.value();
63✔
556

557
        if (in_progress) {
63✔
558
                return {
559
                        Result::Failed | Result::NoRollbackNecessary,
560
                        error::Error(
561
                                make_error_condition(errc::operation_in_progress),
2✔
562
                                "Update already in progress. Please commit or roll back first")};
2✔
563
        }
564

565
        auto err = PrepareContext(ctx);
62✔
566
        if (err != error::NoError) {
62✔
UNCOV
567
                return {Result::Failed, err};
×
568
        }
569

570
        ctx.artifact_src = src;
62✔
571
        ctx.verify_signature = verify_signature;
62✔
572
        ctx.options = options;
62✔
573

574
        StateMachine state_machine {ctx};
124✔
575

576
        for (const auto &state : ctx.stop_before) {
67✔
577
                err = state_machine.AddStopBeforeState(state);
5✔
578
                if (err != error::NoError) {
5✔
UNCOV
579
                        return {Result::Failed, err};
×
580
                }
581
        }
582

583
        state_machine.Run();
62✔
584

585
        return ctx.result_and_error;
62✔
586
}
587

588
ResultAndError Resume(Context &ctx) {
13✔
589
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
13✔
590
        if (!exp_in_progress) {
13✔
UNCOV
591
                return {Result::Failed, exp_in_progress.error()};
×
592
        }
593
        auto &in_progress = exp_in_progress.value();
13✔
594

595
        if (!in_progress) {
13✔
596
                return {
597
                        Result::NoUpdateInProgress,
UNCOV
598
                        context::MakeError(context::NoUpdateInProgressError, "Cannot resume")};
×
599
        }
600
        ctx.state_data = in_progress.value();
13✔
601

602
        auto err = PrepareContext(ctx);
13✔
603
        if (err != error::NoError) {
13✔
UNCOV
604
                return {Result::Failed, err};
×
605
        }
606

607
        err = PrepareContextFromStateData(ctx, ctx.state_data);
13✔
608
        if (err != error::NoError) {
13✔
UNCOV
609
                return {Result::Failed, err};
×
610
        }
611

612
        StateMachine state_machine {ctx};
26✔
613

614
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
13✔
615
        if (err != error::NoError) {
13✔
UNCOV
616
                return {Result::Failed, err};
×
617
        }
618

619
        for (const auto &state : ctx.stop_before) {
21✔
620
                err = state_machine.AddStopBeforeState(state);
8✔
621
                if (err != error::NoError) {
8✔
UNCOV
622
                        return {Result::Failed, err};
×
623
                }
624
        }
625

626
        state_machine.Run();
13✔
627

628
        return ctx.result_and_error;
13✔
629
}
630

631
ResultAndError Commit(Context &ctx) {
6✔
632
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
6✔
633
        if (!exp_in_progress) {
6✔
UNCOV
634
                return {Result::Failed, exp_in_progress.error()};
×
635
        }
636
        auto &in_progress = exp_in_progress.value();
6✔
637

638
        if (!in_progress) {
6✔
639
                return {
640
                        Result::NoUpdateInProgress,
641
                        context::MakeError(context::NoUpdateInProgressError, "Cannot commit")};
2✔
642
        }
643
        ctx.state_data = in_progress.value();
5✔
644

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

655
        auto err = PrepareContext(ctx);
5✔
656
        if (err != error::NoError) {
5✔
UNCOV
657
                return {Result::Failed, err};
×
658
        }
659

660
        err = PrepareContextFromStateData(ctx, ctx.state_data);
5✔
661
        if (err != error::NoError) {
5✔
UNCOV
662
                return {Result::Failed, err};
×
663
        }
664

665
        StateMachine state_machine {ctx};
10✔
666

667
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
5✔
668
        if (err != error::NoError) {
5✔
UNCOV
669
                return {Result::Failed, err};
×
670
        }
671

672
        for (const auto &state : ctx.stop_before) {
7✔
673
                err = state_machine.AddStopBeforeState(state);
2✔
674
                if (err != error::NoError) {
2✔
UNCOV
675
                        return {Result::Failed, err};
×
676
                }
677
        }
678

679
        state_machine.Run();
5✔
680

681
        return ctx.result_and_error;
5✔
682
}
683

684
ResultAndError Rollback(Context &ctx) {
8✔
685
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
8✔
686
        if (!exp_in_progress) {
8✔
UNCOV
687
                return {Result::Failed, exp_in_progress.error()};
×
688
        }
689
        auto &in_progress = exp_in_progress.value();
8✔
690

691
        if (!in_progress) {
8✔
692
                return {
693
                        Result::NoUpdateInProgress,
694
                        context::MakeError(context::NoUpdateInProgressError, "Cannot roll back")};
2✔
695
        }
696
        ctx.state_data = in_progress.value();
7✔
697

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

712
        auto err = PrepareContext(ctx);
7✔
713
        if (err != error::NoError) {
7✔
UNCOV
714
                return {Result::Failed, err};
×
715
        }
716

717
        err = PrepareContextFromStateData(ctx, ctx.state_data);
7✔
718
        if (err != error::NoError) {
7✔
UNCOV
719
                return {Result::Failed, err};
×
720
        }
721

722
        StateMachine state_machine {ctx};
14✔
723
        state_machine.StartOnRollback();
724
        if (err != error::NoError) {
7✔
UNCOV
725
                return {Result::Failed, err};
×
726
        }
727

728
        for (const auto &state : ctx.stop_before) {
8✔
729
                err = state_machine.AddStopBeforeState(state);
1✔
730
                if (err != error::NoError) {
1✔
UNCOV
731
                        return {Result::Failed, err};
×
732
                }
733
        }
734

735
        state_machine.Run();
7✔
736

737
        return ctx.result_and_error;
7✔
738
}
739

740
} // namespace standalone
741
} // namespace update
742
} // 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