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

mendersoftware / mender / 1421288278

21 Aug 2024 09:30AM UTC coverage: 76.154%. First build
1421288278

push

gitlab-ci

kacf
feat: Expand `--stop-after` functionality.

Changelog: Adjust states allowed for the `--stop-after` flag.
These are the allowed states:
* `Download_Leave`
* `ArtifactInstall_Leave`
* `ArtifactInstall_Error`
* `ArtifactCommit` (exits before `ArtifactCommit_Leave`)
* `ArtifactCommit_Error`
* `ArtifactRollback_Leave`
* `ArtifactFailure_Leave`

Changelog: Add `--stop-after` flag to `commit` and `rollback`
commands.

Changelog: Allow `--stop-after` flag to be specified multiple times.

Ticket: MEN-7115

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

145 of 167 new or added lines in 4 files covered. (86.83%)

7377 of 9687 relevant lines covered (76.15%)

11284.59 hits per line

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

83.89
/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) {
83✔
40
        StateDataKeys keys;
41
        StateData dst;
166✔
42

43
        auto exp_bytes = db.Read(context::MenderContext::standalone_state_key);
83✔
44
        if (!exp_bytes) {
83✔
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()));
38✔
54
        if (!exp_json) {
19✔
55
                return expected::unexpected(exp_json.error());
×
56
        }
57
        auto &json = exp_json.value();
19✔
58

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

65
        if (dst.version != 1 && dst.version != context::MenderContext::standalone_data_version) {
19✔
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);
19✔
72
        if (!exp_string) {
19✔
73
                return expected::unexpected(exp_string.error());
×
74
        }
75
        dst.artifact_name = exp_string.value();
19✔
76

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

83
        auto exp_map = json::Get<json::KeyValueMap>(json, keys.artifact_provides, json::MissingOk::No);
19✔
84
        if (exp_map) {
19✔
85
                dst.artifact_provides = exp_map.value();
18✔
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);
19✔
92
        if (exp_array) {
19✔
93
                dst.artifact_clears_provides = exp_array.value();
18✔
94
        } else {
95
                dst.artifact_clears_provides.reset();
96
        }
97

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

104
        if (dst.version == 1) {
19✔
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);
38✔
119
                if (!exp_string) {
19✔
120
                        return expected::unexpected(exp_string.error());
×
121
                }
122
                dst.in_state = exp_string.value();
19✔
123

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

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

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

142
        if (dst.payload_types.size() == 0) {
19✔
143
                return expected::unexpected(context::MakeError(
×
144
                        context::DatabaseValueError, "`" + keys.payload_types + "` is empty"));
×
145
        }
146
        if (dst.payload_types.size() >= 2) {
19✔
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;
19✔
153
}
154

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

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

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

169
        ss << ",";
265✔
170
        ss << "\"" << keys.artifact_group << "\":\"" << data.artifact_group << "\"";
265✔
171

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

184
        if (data.artifact_provides) {
265✔
185
                ss << ",";
259✔
186
                ss << "\"" << keys.artifact_provides << "\": {";
259✔
187
                bool first = true;
188
                for (auto elem : data.artifact_provides.value()) {
1,554✔
189
                        if (!first) {
518✔
190
                                ss << ",";
259✔
191
                        }
192
                        ss << "\"" << elem.first << "\":\"" << elem.second << "\"";
518✔
193
                        first = false;
194
                }
195
                ss << "}";
259✔
196
        }
197

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

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

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

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

218
        ss << "}";
265✔
219

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

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

226
StateMachine::StateMachine(Context &ctx) :
80✔
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_artifact_install_state_ {StateData::kInStateArtifactInstall_Enter},
244
        artifact_install_enter_state_ {
245
                executor::State::ArtifactInstall,
246
                executor::Action::Enter,
247
                executor::OnError::Fail,
248
                Result::InstallFailed | Result::Failed},
249
        artifact_install_leave_state_ {
250
                executor::State::ArtifactInstall,
251
                executor::Action::Leave,
252
                executor::OnError::Fail,
253
                Result::InstallFailed | Result::Failed},
254
        artifact_install_error_state_ {
255
                executor::State::ArtifactInstall,
256
                executor::Action::Error,
257
                executor::OnError::Ignore,
258
                Result::NoResult},
259
        save_artifact_commit_state_ {StateData::kInStateArtifactCommit_Enter},
260
        artifact_commit_enter_state_ {
261
                executor::State::ArtifactCommit,
262
                executor::Action::Enter,
263
                executor::OnError::Fail,
264
                Result::CommitFailed | Result::Failed},
265
        save_post_artifact_commit_state_ {StateData::kInStatePostArtifactCommit},
266
        save_artifact_commit_leave_state_ {StateData::kInStateArtifactCommit_Leave},
267
        artifact_commit_leave_state_ {
268
                executor::State::ArtifactCommit,
269
                executor::Action::Leave,
270
                executor::OnError::Ignore,
271
                Result::CommitFailed | Result::Failed | Result::FailedInPostCommit},
272
        artifact_commit_error_state_ {
273
                executor::State::ArtifactCommit,
274
                executor::Action::Error,
275
                executor::OnError::Ignore,
276
                Result::NoResult},
277
        save_artifact_rollback_state_ {StateData::kInStateArtifactRollback_Enter},
278
        artifact_rollback_enter_state_ {
279
                executor::State::ArtifactRollback,
280
                executor::Action::Enter,
281
                executor::OnError::Ignore,
282
                Result::Failed | Result::RollbackFailed},
283
        artifact_rollback_leave_state_ {
284
                executor::State::ArtifactRollback,
285
                executor::Action::Leave,
286
                executor::OnError::Ignore,
287
                Result::NoResult},
288
        save_artifact_failure_state_ {StateData::kInStateArtifactFailure_Enter},
289
        artifact_failure_enter_state_ {
290
                executor::State::ArtifactFailure,
291
                executor::Action::Enter,
292
                executor::OnError::Ignore,
293
                Result::Failed | Result::RollbackFailed},
294
        artifact_failure_leave_state_ {
295
                executor::State::ArtifactFailure,
296
                executor::Action::Leave,
297
                executor::OnError::Ignore,
298
                Result::NoResult},
299
        save_cleanup_state_ {StateData::kInStateCleanup},
300
        exit_state_ {loop_},
80✔
301
        start_state_ {&prepare_download_state_},
302
        state_machine_ {*start_state_} {
80✔
303
        using tf = common::state_machine::TransitionFlag;
304
        using se = StateEvent;
305
        auto &s = state_machine_;
306

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

312
        s.AddTransition(download_enter_state_,             se::Success,              download_state_,                   tf::Immediate);
80✔
313
        s.AddTransition(download_enter_state_,             se::Failure,              download_error_state_,             tf::Immediate);
80✔
314

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

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

321
        s.AddTransition(download_error_state_,             se::Success,              save_cleanup_state_,               tf::Immediate);
80✔
322
        s.AddTransition(download_error_state_,             se::Failure,              save_cleanup_state_,               tf::Immediate);
80✔
323

324
        s.AddTransition(save_artifact_install_state_,      se::Success,              artifact_install_enter_state_,     tf::Immediate);
80✔
325
        s.AddTransition(save_artifact_install_state_,      se::Failure,              save_cleanup_state_,               tf::Immediate);
80✔
326

327
        s.AddTransition(artifact_install_enter_state_,     se::Success,              artifact_install_state_,           tf::Immediate);
80✔
328
        s.AddTransition(artifact_install_enter_state_,     se::Failure,              artifact_install_error_state_,     tf::Immediate);
80✔
329

330
        s.AddTransition(artifact_install_state_,           se::Success,              artifact_install_leave_state_,     tf::Immediate);
80✔
331
        s.AddTransition(artifact_install_state_,           se::Failure,              artifact_install_error_state_,     tf::Immediate);
80✔
332

333
        s.AddTransition(artifact_install_leave_state_,     se::Success,              save_artifact_commit_state_,       tf::Immediate);
80✔
334
        s.AddTransition(artifact_install_leave_state_,     se::Failure,              artifact_install_error_state_,     tf::Immediate);
80✔
335

336
        s.AddTransition(artifact_install_error_state_,     se::Success,              rollback_query_state_,             tf::Immediate);
80✔
337
        s.AddTransition(artifact_install_error_state_,     se::Failure,              rollback_query_state_,             tf::Immediate);
80✔
338

339
        s.AddTransition(save_artifact_commit_state_,       se::Success,              reboot_and_rollback_query_state_,  tf::Immediate);
80✔
340
        s.AddTransition(save_artifact_commit_state_,       se::Failure,              reboot_and_rollback_query_state_,  tf::Immediate);
80✔
341

342
        s.AddTransition(reboot_and_rollback_query_state_,  se::Success,              artifact_commit_enter_state_,      tf::Immediate);
80✔
343
        s.AddTransition(reboot_and_rollback_query_state_,  se::Failure,              rollback_query_state_,             tf::Immediate);
80✔
344
        s.AddTransition(reboot_and_rollback_query_state_,  se::NeedsInteraction,     exit_state_,                       tf::Immediate);
80✔
345

346
        s.AddTransition(artifact_commit_enter_state_,      se::Success,              artifact_commit_state_,            tf::Immediate);
80✔
347
        s.AddTransition(artifact_commit_enter_state_,      se::Failure,              artifact_commit_error_state_,      tf::Immediate);
80✔
348

349
        s.AddTransition(artifact_commit_state_,            se::Success,              save_post_artifact_commit_state_,  tf::Immediate);
80✔
350
        s.AddTransition(artifact_commit_state_,            se::Failure,              artifact_commit_error_state_,      tf::Immediate);
80✔
351

352
        // The reason we have two save states in a row here, is that using the `--stop-after`
353
        // option, one can exit at the point of save_post_artifact_commit_state, and it is allowed
354
        // both to resume and roll back from here. However, once we have started executing the
355
        // `ArtifactCommit_Leave` scripts, it is no longer allowed to roll back, therefore it is
356
        // important to save that as a *separate* state.
357
        s.AddTransition(save_post_artifact_commit_state_,  se::Success,              save_artifact_commit_leave_state_, tf::Immediate);
80✔
358
        s.AddTransition(save_post_artifact_commit_state_,  se::Failure,              artifact_commit_error_state_,      tf::Immediate);
80✔
359

360
        s.AddTransition(save_artifact_commit_leave_state_, se::Success,              artifact_commit_leave_state_,      tf::Immediate);
80✔
361
        s.AddTransition(save_artifact_commit_leave_state_, se::Failure,              artifact_commit_error_state_,      tf::Immediate);
80✔
362

363
        s.AddTransition(artifact_commit_leave_state_,      se::Success,              save_cleanup_state_,               tf::Immediate);
80✔
364
        s.AddTransition(artifact_commit_leave_state_,      se::Failure,              save_cleanup_state_,               tf::Immediate);
80✔
365

366
        s.AddTransition(rollback_query_state_,             se::Success,              save_artifact_rollback_state_,     tf::Immediate);
80✔
367
        s.AddTransition(rollback_query_state_,             se::NothingToDo,          save_artifact_failure_state_,      tf::Immediate);
80✔
368
        s.AddTransition(rollback_query_state_,             se::Failure,              save_artifact_failure_state_,      tf::Immediate);
80✔
369
        s.AddTransition(rollback_query_state_,             se::NeedsInteraction,     exit_state_,                       tf::Immediate);
80✔
370

371
        s.AddTransition(artifact_commit_error_state_,      se::Success,              rollback_query_state_,             tf::Immediate);
80✔
372
        s.AddTransition(artifact_commit_error_state_,      se::Failure,              rollback_query_state_,             tf::Immediate);
80✔
373

374
        s.AddTransition(save_artifact_rollback_state_,     se::Success,              artifact_rollback_enter_state_,    tf::Immediate);
80✔
375
        s.AddTransition(save_artifact_rollback_state_,     se::Failure,              artifact_rollback_enter_state_,    tf::Immediate);
80✔
376

377
        s.AddTransition(artifact_rollback_enter_state_,    se::Success,              artifact_rollback_state_,          tf::Immediate);
80✔
378
        s.AddTransition(artifact_rollback_enter_state_,    se::Failure,              artifact_rollback_state_,          tf::Immediate);
80✔
379

380
        s.AddTransition(artifact_rollback_state_,          se::Success,              artifact_rollback_leave_state_,    tf::Immediate);
80✔
381
        s.AddTransition(artifact_rollback_state_,          se::Failure,              artifact_rollback_leave_state_,    tf::Immediate);
80✔
382

383
        s.AddTransition(artifact_rollback_leave_state_,    se::Success,              save_artifact_failure_state_,      tf::Immediate);
80✔
384
        s.AddTransition(artifact_rollback_leave_state_,    se::Failure,              save_artifact_failure_state_,      tf::Immediate);
80✔
385

386
        s.AddTransition(save_artifact_failure_state_,      se::Success,              artifact_failure_enter_state_,     tf::Immediate);
80✔
387
        s.AddTransition(save_artifact_failure_state_,      se::Failure,              artifact_failure_enter_state_,     tf::Immediate);
80✔
388

389
        s.AddTransition(artifact_failure_enter_state_,     se::Success,              artifact_failure_state_,           tf::Immediate);
80✔
390
        s.AddTransition(artifact_failure_enter_state_,     se::Failure,              artifact_failure_state_,           tf::Immediate);
80✔
391

392
        s.AddTransition(artifact_failure_state_,           se::Success,              artifact_failure_leave_state_,     tf::Immediate);
80✔
393
        s.AddTransition(artifact_failure_state_,           se::Failure,              artifact_failure_leave_state_,     tf::Immediate);
80✔
394

395
        s.AddTransition(artifact_failure_leave_state_,     se::Success,              save_cleanup_state_,               tf::Immediate);
80✔
396
        s.AddTransition(artifact_failure_leave_state_,     se::Failure,              save_cleanup_state_,               tf::Immediate);
80✔
397

398
        s.AddTransition(save_cleanup_state_,               se::Success,              cleanup_state_,                    tf::Immediate);
80✔
399
        s.AddTransition(save_cleanup_state_,               se::Failure,              cleanup_state_,                    tf::Immediate);
80✔
400

401
        s.AddTransition(cleanup_state_,                    se::Success,              exit_state_,                       tf::Immediate);
80✔
402
        s.AddTransition(cleanup_state_,                    se::Failure,              exit_state_,                       tf::Immediate);
80✔
403
        // clang-format on
404
}
80✔
405

406
void StateMachine::Run() {
80✔
407
        common::state_machine::StateMachineRunner<Context, StateEvent> runner {context_};
160✔
408
        runner.AddStateMachine(state_machine_);
80✔
409
        runner.AttachToEventLoop(loop_);
80✔
410

411
        state_machine_.SetState(*start_state_);
80✔
412

413
        loop_.Run();
80✔
414
}
80✔
415

416
error::Error StateMachine::SetStartStateFromStateData(const string &in_state) {
11✔
417
        if (in_state == StateData::kInStateArtifactInstall_Enter) {
11✔
418
                start_state_ = &artifact_install_enter_state_;
1✔
419
        } else if (in_state == StateData::kInStateArtifactCommit_Enter) {
10✔
420
                start_state_ = &artifact_commit_enter_state_;
6✔
421
        } else if (in_state == StateData::kInStatePostArtifactCommit) {
4✔
422
                start_state_ = &save_artifact_commit_leave_state_;
2✔
423
        } else if (in_state == StateData::kInStateArtifactCommit_Leave) {
2✔
424
                start_state_ = &artifact_commit_leave_state_;
×
425
        } else if (in_state == StateData::kInStateCleanup) {
2✔
426
                start_state_ = &cleanup_state_;
1✔
427
        } else if (in_state == StateData::kInStateArtifactRollback_Enter) {
1✔
428
                start_state_ = &artifact_rollback_enter_state_;
×
429
        } else if (in_state == StateData::kInStateArtifactFailure_Enter) {
1✔
430
                start_state_ = &artifact_failure_enter_state_;
1✔
431
        } else {
432
                return context::MakeError(
433
                        context::DatabaseValueError, "Invalid InState in database " + in_state);
×
434
        }
435

436
        return error::NoError;
11✔
437
}
438

439
error::Error StateMachine::AddStopAfterState(const string &state) {
10✔
440
        using tf = common::state_machine::TransitionFlag;
441
        using se = StateEvent;
442
        auto &s = state_machine_;
10✔
443

444
        // Replace transition in state machine in order to exit at given point.
445
        if (state == "Download_Leave") {
10✔
446
                s.AddTransition(save_artifact_install_state_, se::Success, exit_state_, tf::Immediate);
1✔
447

448
        } else if (state == "ArtifactInstall_Leave") {
9✔
449
                s.AddTransition(save_artifact_commit_state_, se::Success, exit_state_, tf::Immediate);
3✔
450

451
        } else if (state == "ArtifactCommit") {
6✔
452
                s.AddTransition(save_post_artifact_commit_state_, se::Success, exit_state_, tf::Immediate);
3✔
453

454
        } else if (state == "ArtifactInstall_Error" or state == "ArtifactCommit_Error") {
3✔
455
                // Either of these two states could be entered after an error, depending on rollback
456
                // support.
457
                s.AddTransition(save_artifact_rollback_state_, se::Success, exit_state_, tf::Immediate);
1✔
458
                s.AddTransition(save_artifact_failure_state_, se::Success, exit_state_, tf::Immediate);
1✔
459

460
        } else if (state == "ArtifactRollback_Leave") {
2✔
461
                s.AddTransition(save_artifact_failure_state_, se::Success, exit_state_, tf::Immediate);
1✔
462

463
        } else if (state == "ArtifactFailure_Leave") {
1✔
464
                s.AddTransition(save_cleanup_state_, se::Success, exit_state_, tf::Immediate);
1✔
465

466
        } else if (state != "") {
×
467
                return context::MakeError(
NEW
468
                        context::ValueError, "Cannot stop after unsupported state " + state);
×
469
        }
470

471
        return error::NoError;
10✔
472
}
473

474
void StateMachine::StartOnRollback() {
×
475
        start_state_ = &rollback_query_state_;
7✔
476
}
×
477

478
error::Error PrepareContext(Context &ctx) {
80✔
479
        const auto &conf = ctx.main_context.GetConfig();
80✔
480
        const auto &paths {conf.paths};
481
        ctx.script_runner.reset(new executor::ScriptRunner(
482
                ctx.loop,
483
                chrono::seconds {conf.state_script_timeout_seconds},
484
                chrono::seconds {conf.state_script_retry_interval_seconds},
485
                chrono::seconds {conf.state_script_retry_timeout_seconds},
486
                paths.GetArtScriptsPath(),
80✔
487
                paths.GetRootfsScriptsPath()));
160✔
488

489
        return error::NoError;
80✔
490
}
491

492
error::Error PrepareContextFromStateData(Context &ctx, const StateData &data) {
18✔
493
        ctx.update_module.reset(
494
                new update_module::UpdateModule(ctx.main_context, data.payload_types[0]));
18✔
495

496
        if (data.payload_types[0] == "rootfs-image") {
18✔
497
                // Special case for rootfs-image upgrades. See comments inside the function.
498
                auto err = ctx.update_module->EnsureRootfsImageFileTree(
499
                        ctx.update_module->GetUpdateModuleWorkDir());
18✔
500
                if (err != error::NoError) {
18✔
501
                        return err;
×
502
                }
503
        }
504

505
        if (data.failed) {
18✔
506
                ctx.result_and_error.result = ctx.result_and_error.result | Result::Failed;
×
507
        }
508

509
        if (data.rolled_back) {
18✔
510
                ctx.result_and_error.result = ctx.result_and_error.result | Result::RolledBack;
2✔
511
        }
512

513
        return error::NoError;
18✔
514
}
515

516
ResultAndError Install(
63✔
517
        Context &ctx,
518
        const string &src,
519
        const artifact::config::Signature verify_signature,
520
        InstallOptions options) {
521
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
63✔
522
        if (!exp_in_progress) {
63✔
523
                return {Result::Failed, exp_in_progress.error()};
×
524
        }
525
        auto &in_progress = exp_in_progress.value();
63✔
526

527
        if (in_progress) {
63✔
528
                return {
529
                        Result::Failed | Result::NoRollbackNecessary,
530
                        error::Error(
531
                                make_error_condition(errc::operation_in_progress),
2✔
532
                                "Update already in progress. Please commit or roll back first")};
2✔
533
        }
534

535
        auto err = PrepareContext(ctx);
62✔
536
        if (err != error::NoError) {
62✔
537
                return {Result::Failed, err};
×
538
        }
539

540
        ctx.artifact_src = src;
62✔
541
        ctx.verify_signature = verify_signature;
62✔
542
        ctx.options = options;
62✔
543

544
        StateMachine state_machine {ctx};
124✔
545

546
        for (const auto &state : ctx.stop_after) {
68✔
547
                err = state_machine.AddStopAfterState(state);
6✔
548
                if (err != error::NoError) {
6✔
NEW
549
                        return {Result::Failed, err};
×
550
                }
551
        }
552

553
        state_machine.Run();
62✔
554

555
        return ctx.result_and_error;
62✔
556
}
557

558
ResultAndError Resume(Context &ctx) {
6✔
559
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
6✔
560
        if (!exp_in_progress) {
6✔
561
                return {Result::Failed, exp_in_progress.error()};
×
562
        }
563
        auto &in_progress = exp_in_progress.value();
6✔
564

565
        if (!in_progress) {
6✔
566
                return {
567
                        Result::NoUpdateInProgress,
568
                        context::MakeError(context::NoUpdateInProgressError, "Cannot resume")};
×
569
        }
570
        ctx.state_data = in_progress.value();
6✔
571

572
        auto err = PrepareContext(ctx);
6✔
573
        if (err != error::NoError) {
6✔
574
                return {Result::Failed, err};
×
575
        }
576

577
        err = PrepareContextFromStateData(ctx, ctx.state_data);
6✔
578
        if (err != error::NoError) {
6✔
579
                return {Result::Failed, err};
×
580
        }
581

582
        StateMachine state_machine {ctx};
12✔
583

584
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
6✔
585
        if (err != error::NoError) {
6✔
586
                return {Result::Failed, err};
×
587
        }
588

589
        for (const auto &state : ctx.stop_after) {
7✔
590
                err = state_machine.AddStopAfterState(state);
1✔
591
                if (err != error::NoError) {
1✔
NEW
592
                        return {Result::Failed, err};
×
593
                }
594
        }
595

596
        state_machine.Run();
6✔
597

598
        return ctx.result_and_error;
6✔
599
}
600

601
ResultAndError Commit(Context &ctx) {
6✔
602
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
6✔
603
        if (!exp_in_progress) {
6✔
604
                return {Result::Failed, exp_in_progress.error()};
×
605
        }
606
        auto &in_progress = exp_in_progress.value();
6✔
607

608
        if (!in_progress) {
6✔
609
                return {
610
                        Result::NoUpdateInProgress,
611
                        context::MakeError(context::NoUpdateInProgressError, "Cannot commit")};
2✔
612
        }
613
        ctx.state_data = in_progress.value();
5✔
614

615
        if (ctx.state_data.in_state != StateData::kInStateArtifactCommit_Enter) {
5✔
616
                return {
617
                        Result::Failed,
618
                        context::MakeError(
619
                                context::WrongOperationError,
620
                                "Cannot commit from this state. "
621
                                "Make sure that the `install` command has run successfully and the device is expecting a commit.")};
×
622
        }
623

624
        auto err = PrepareContext(ctx);
5✔
625
        if (err != error::NoError) {
5✔
626
                return {Result::Failed, err};
×
627
        }
628

629
        err = PrepareContextFromStateData(ctx, ctx.state_data);
5✔
630
        if (err != error::NoError) {
5✔
631
                return {Result::Failed, err};
×
632
        }
633

634
        StateMachine state_machine {ctx};
10✔
635

636
        err = state_machine.SetStartStateFromStateData(ctx.state_data.in_state);
5✔
637
        if (err != error::NoError) {
5✔
638
                return {Result::Failed, err};
×
639
        }
640

641
        for (const auto &state : ctx.stop_after) {
7✔
642
                err = state_machine.AddStopAfterState(state);
2✔
643
                if (err != error::NoError) {
2✔
NEW
644
                        return {Result::Failed, err};
×
645
                }
646
        }
647

648
        state_machine.Run();
5✔
649

650
        return ctx.result_and_error;
5✔
651
}
652

653
ResultAndError Rollback(Context &ctx) {
8✔
654
        auto exp_in_progress = LoadStateData(ctx.main_context.GetMenderStoreDB());
8✔
655
        if (!exp_in_progress) {
8✔
656
                return {Result::Failed, exp_in_progress.error()};
×
657
        }
658
        auto &in_progress = exp_in_progress.value();
8✔
659

660
        if (!in_progress) {
8✔
661
                return {
662
                        Result::NoUpdateInProgress,
663
                        context::MakeError(context::NoUpdateInProgressError, "Cannot roll back")};
2✔
664
        }
665
        ctx.state_data = in_progress.value();
7✔
666

667
        if (ctx.state_data.in_state != StateData::kInStateArtifactInstall_Enter
7✔
668
                and ctx.state_data.in_state != StateData::kInStateArtifactCommit_Enter
7✔
669
                and ctx.state_data.in_state != StateData::kInStatePostArtifactCommit
2✔
670
                and ctx.state_data.in_state != StateData::kInStateArtifactRollback_Enter) {
8✔
671
                return {
672
                        Result::Failed,
673
                        context::MakeError(
674
                                context::WrongOperationError,
675
                                "Cannot roll back from this state. "
676
                                "Use `resume` to complete the current install in order to start a new one.")};
×
677
        }
678

679
        auto err = PrepareContext(ctx);
7✔
680
        if (err != error::NoError) {
7✔
681
                return {Result::Failed, err};
×
682
        }
683

684
        err = PrepareContextFromStateData(ctx, ctx.state_data);
7✔
685
        if (err != error::NoError) {
7✔
686
                return {Result::Failed, err};
×
687
        }
688

689
        StateMachine state_machine {ctx};
14✔
690
        state_machine.StartOnRollback();
691
        if (err != error::NoError) {
7✔
692
                return {Result::Failed, err};
×
693
        }
694

695
        for (const auto &state : ctx.stop_after) {
8✔
696
                err = state_machine.AddStopAfterState(state);
1✔
697
                if (err != error::NoError) {
1✔
NEW
698
                        return {Result::Failed, err};
×
699
                }
700
        }
701

702
        state_machine.Run();
7✔
703

704
        return ctx.result_and_error;
7✔
705
}
706

707
} // namespace standalone
708
} // namespace update
709
} // 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