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

mendersoftware / mender / 947394036

pending completion
947394036

push

gitlab-ci

kacf
chore: Treat events with no state transitions as fatal.

This was discussed with the team members. Since an unhandled event is
almost guaranteed to hang the state machine, then it's better to
terminate and let systemd try to restart us, in the hopes that
recovery will still work.

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

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

4268 of 5997 relevant lines covered (71.17%)

148.52 hits per line

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

0.0
/mender-update/daemon/context.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/daemon/context.hpp>
16

17
#include <common/common.hpp>
18
#include <common/log.hpp>
19

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

24
namespace common = mender::common;
25
namespace log = mender::common::log;
26
namespace main_context = mender::update::context;
27

28
const int kStateDataVersion = 2;
29

30
// The maximum times we are allowed to move through update states. If this is exceeded then the
31
// update will be forcefully aborted. This can happen if we are in a reboot loop, for example.
32
const int kMaxStateDataStoreCount = 50;
33

34
ExpectedStateData ApiResponseJsonToStateData(const json::Json &json) {
×
35
        StateData data;
×
36

37
        expected::ExpectedString str = json.Get("id").and_then(json::ToString);
×
38
        if (!str) {
×
39
                return expected::unexpected(str.error().WithContext("Could not get deployment ID"));
×
40
        }
41
        data.update_info.id = str.value();
×
42

43
        str = json.Get("artifact")
×
44
                          .and_then([](const json::Json &json) { return json.Get("source"); })
×
45
                          .and_then([](const json::Json &json) { return json.Get("uri"); })
×
46
                          .and_then(json::ToString);
×
47
        if (!str) {
×
48
                return expected::unexpected(
×
49
                        str.error().WithContext("Could not get artifact URI for deployment"));
×
50
        }
51
        data.update_info.artifact.source.uri = str.value();
×
52
        log::Debug("Artifact Download URL: " + data.update_info.artifact.source.uri);
×
53

54
        str = json.Get("artifact")
×
55
                          .and_then([](const json::Json &json) { return json.Get("source"); })
×
56
                          .and_then([](const json::Json &json) { return json.Get("expire"); })
×
57
                          .and_then(json::ToString);
×
58
        if (str) {
×
59
                data.update_info.artifact.source.expire = str.value();
×
60
                // If it's not available, we don't care.
61
        }
62

63
        // For later: Update Control Maps should be handled here.
64

65
        // Note: There is more information available in the response than we collect here, but we
66
        // prefer to get the information from the artifact instead, since it is the authoritative
67
        // source. And it's also signed, unlike the response.
68

69
        return data;
×
70
}
71

72
// Database keys
73
const string Context::kRollbackNotSupported = "rollback-not-supported";
74
const string Context::kRollbackSupported = "rollback-supported";
75

76
string SupportsRollbackToDbString(bool support) {
×
77
        return support ? Context::kRollbackSupported : Context::kRollbackNotSupported;
×
78
}
79

80
expected::ExpectedBool DbStringToSupportsRollback(const string &str) {
×
81
        if (str == Context::kRollbackSupported) {
×
82
                return true;
×
83
        } else if (str == Context::kRollbackNotSupported) {
×
84
                return false;
×
85
        } else {
86
                return expected::unexpected(main_context::MakeError(
×
87
                        main_context::DatabaseValueError,
88
                        "\"" + str + "\" is not a valid value for SupportsRollback"));
×
89
        }
90
}
91

92
// Database keys
93
const string Context::kRebootTypeNone = "";
94
const string Context::kRebootTypeCustom = "reboot-type-custom";
95
const string Context::kRebootTypeAutomatic = "reboot-type-automatic";
96

97
string NeedsRebootToDbString(update_module::RebootAction action) {
×
98
        switch (action) {
×
99
        case update_module::RebootAction::No:
×
100
                return Context::kRebootTypeNone;
×
101
        case update_module::RebootAction::Automatic:
×
102
                return Context::kRebootTypeAutomatic;
×
103
        case update_module::RebootAction::Yes:
×
104
                return Context::kRebootTypeCustom;
×
105
        default:
×
106
                // Should not happen.
107
                assert(false);
×
108
                return Context::kRebootTypeNone;
109
        }
110
}
111

112
update_module::ExpectedRebootAction DbStringToNeedsReboot(const string &str) {
×
113
        if (str == Context::kRebootTypeNone) {
×
114
                return update_module::RebootAction::No;
×
115
        } else if (str == Context::kRebootTypeAutomatic) {
×
116
                return update_module::RebootAction::Automatic;
×
117
        } else if (str == Context::kRebootTypeCustom) {
×
118
                return update_module::RebootAction::Yes;
×
119
        } else {
120
                return expected::unexpected(main_context::MakeError(
×
121
                        main_context::DatabaseValueError,
122
                        "\"" + str + "\" is not a valid value for RebootRequested"));
×
123
        }
124
}
125

126
void StateData::FillUpdateDataFromArtifact(artifact::PayloadHeaderView &view) {
×
127
        version = view.version;
×
128
        auto &artifact = update_info.artifact;
×
129
        auto &header = view.header;
×
130
        artifact.compatible_devices = header.header_info.depends.device_type;
×
131
        artifact.payload_types = {header.payload_type};
×
132
        artifact.artifact_name = header.artifact_name;
×
133
        artifact.artifact_group = header.artifact_group;
×
134
        if (header.type_info.artifact_provides) {
×
135
                artifact.type_info_provides = header.type_info.artifact_provides.value();
×
136
        } else {
137
                artifact.type_info_provides.clear();
×
138
        }
139
        if (header.type_info.clears_artifact_provides) {
×
140
                artifact.clears_artifact_provides = header.type_info.clears_artifact_provides.value();
×
141
        } else {
142
                artifact.clears_artifact_provides.clear();
×
143
        }
144
}
×
145

146
Context::Context(main_context::MenderContext &mender_context, events::EventLoop &event_loop) :
×
147
        mender_context(mender_context),
148
        event_loop(event_loop),
149
        http_client(http::ClientConfig(mender_context.GetConfig().server_url), event_loop),
×
150
        download_client(http::ClientConfig(mender_context.GetConfig().server_url), event_loop),
×
151
        deployment_client(make_shared<deployments::DeploymentClient>()) {
×
152
}
×
153

154
///////////////////////////////////////////////////////////////////////////////////////////////////
155
// Values for various states in the database.
156
///////////////////////////////////////////////////////////////////////////////////////////////////
157

158
// In use by current client. Some of the variable names have been updated from the Golang version,
159
// but the database strings are the same. Some naming is inconsistent, this is for historical
160
// reasons, and it's better to look at the names for the variables.
161
const string Context::kUpdateStateDownload = "update-store";
162
const string Context::kUpdateStateArtifactInstall = "update-install";
163
const string Context::kUpdateStateArtifactReboot = "reboot";
164
const string Context::kUpdateStateArtifactVerifyReboot = "after-reboot";
165
const string Context::kUpdateStateArtifactCommit = "update-commit";
166
const string Context::kUpdateStateAfterArtifactCommit = "update-after-commit";
167
const string Context::kUpdateStateArtifactRollback = "rollback";
168
const string Context::kUpdateStateArtifactRollbackReboot = "rollback-reboot";
169
const string Context::kUpdateStateArtifactVerifyRollbackReboot = "after-rollback-reboot";
170
const string Context::kUpdateStateArtifactFailure = "update-error";
171
const string Context::kUpdateStateCleanup = "cleanup";
172
const string Context::kUpdateStateStatusReportRetry = "update-retry-report";
173

174
///////////////////////////////////////////////////////////////////////////////////////////////////
175
// Not in use by current client, but were in use by Golang client, and still important to handle
176
// correctly in recovery scenarios.
177
///////////////////////////////////////////////////////////////////////////////////////////////////
178

179
// This client doesn't use it, but it's essentially equivalent to "update-after-commit".
180
const string Context::kUpdateStateUpdateAfterFirstCommit = "update-after-first-commit";
181
// This client doesn't use it, but it's essentially equivalent to "after-rollback-reboot".
182
const string Context::kUpdateStateVerifyRollbackReboot = "verify-rollback-reboot";
183
// No longer used. Since this used to be at the very end of an update, if we encounter it in the
184
// database during startup, we just go back to Idle.
185
const string UpdateStateReportStatusError = "status-report-error";
186

187
///////////////////////////////////////////////////////////////////////////////////////////////////
188
// Not in use. All of these, as well as unknown values, will cause a rollback.
189
///////////////////////////////////////////////////////////////////////////////////////////////////
190

191
// Disable, but distinguish from comments.
192
#if false
193
// These were never actually saved due to not being update states.
194
const string Context::kUpdateStateInit = "init";
195
const string Context::kUpdateStateIdle = "idle";
196
const string Context::kUpdateStateAuthorize = "authorize";
197
const string Context::kUpdateStateAuthorizeWait = "authorize-wait";
198
const string Context::kUpdateStateInventoryUpdate = "inventory-update";
199
const string Context::kUpdateStateInventoryUpdateRetryWait = "inventory-update-retry-wait";
200

201
const string Context::kUpdateStateCheckWait = "check-wait";
202
const string Context::kUpdateStateUpdateCheck = "update-check";
203
const string Context::kUpdateStateUpdateFetch = "update-fetch";
204
const string Context::kUpdateStateUpdateAfterStore = "update-after-store";
205
const string Context::kUpdateStateFetchStoreRetryWait = "fetch-install-retry-wait";
206
const string Context::kUpdateStateUpdateVerify = "update-verify";
207
const string Context::kUpdateStateUpdatePreCommitStatusReportRetry = "update-pre-commit-status-report-retry";
208
const string Context::kUpdateStateUpdateStatusReport = "update-status-report";
209
// Would have been used, but a copy/paste error in the Golang client means that it was never
210
// saved. "after-reboot" is stored twice instead.
211
const string Context::kUpdateStateVerifyReboot = "verify-reboot";
212
const string Context::kUpdateStateError = "error";
213
const string Context::kUpdateStateDone = "finished";
214
const string Context::kUpdateStateUpdateControl = "mender-update-control";
215
const string Context::kUpdateStateUpdateControlPause = "mender-update-control-pause";
216
const string Context::kUpdateStateFetchUpdateControl = "mender-update-control-refresh-maps";
217
const string Context::kUpdateStateFetchRetryUpdateControl = "mender-update-control-retry-refresh-maps";
218
#endif
219

220
///////////////////////////////////////////////////////////////////////////////////////////////////
221
// End of database values.
222
///////////////////////////////////////////////////////////////////////////////////////////////////
223

224
static string GenerateStateDataJson(const StateData &state_data) {
×
225
        stringstream content;
×
226

227
        auto append_vector = [&content](const vector<string> &data) {
×
228
                for (auto entry = data.begin(); entry != data.end(); entry++) {
×
229
                        if (entry != data.begin()) {
×
230
                                content << ",";
×
231
                        }
232
                        content << R"(")" << json::EscapeString(*entry) << R"(")";
×
233
                }
234
        };
×
235

236
        auto append_map = [&content](const unordered_map<string, string> &data) {
×
237
                for (auto entry = data.begin(); entry != data.end(); entry++) {
×
238
                        if (entry != data.begin()) {
×
239
                                content << ",";
×
240
                        }
241
                        content << R"(")" << json::EscapeString(entry->first) << R"(":")"
×
242
                                        << json::EscapeString(entry->second) << R"(")";
×
243
                }
244
        };
×
245

246
        content << "{";
×
247
        {
248
                content << R"("Version":)" << to_string(state_data.version) << ",";
×
249
                content << R"("Name":")" << json::EscapeString(state_data.state) << R"(",)";
×
250
                content << R"("UpdateInfo":{)";
×
251
                {
252
                        auto &update_info = state_data.update_info;
×
253
                        content << R"("Artifact":{)";
×
254
                        {
255
                                auto &artifact = update_info.artifact;
×
256
                                content << R"("Source":{)";
×
257
                                {
258
                                        content << R"("URI":")" << json::EscapeString(artifact.source.uri) << R"(",)";
×
259
                                        content << R"("Expire":")" << json::EscapeString(artifact.source.expire)
×
260
                                                        << R"(")";
×
261
                                }
262
                                content << "},";
×
263

264
                                content << R"("CompatibleDevices":[)";
×
265
                                append_vector(artifact.compatible_devices);
×
266
                                content << "],";
×
267

268
                                content << R"("PayloadTypes":[)";
×
269
                                append_vector(artifact.payload_types);
×
270
                                content << "],";
×
271

272
                                content << R"("ArtifactName":")" << json::EscapeString(artifact.artifact_name)
×
273
                                                << R"(",)";
×
274
                                content << R"("ArtifactGroup":")" << json::EscapeString(artifact.artifact_group)
×
275
                                                << R"(",)";
×
276

277
                                content << R"("TypeInfoProvides":{)";
×
278
                                append_map(artifact.type_info_provides);
×
279
                                content << "},";
×
280

281
                                content << R"("ClearsArtifactProvides":[)";
×
282
                                append_vector(artifact.clears_artifact_provides);
×
283
                                content << "]";
×
284
                        }
285
                        content << "},";
×
286

287
                        content << R"("ID":")" << json::EscapeString(update_info.id) << R"(",)";
×
288

289
                        content << R"("RebootRequested":[)";
×
290
                        append_vector(update_info.reboot_requested);
×
291
                        content << R"(],)";
×
292

293
                        content << R"("SupportsRollback":")"
294
                                        << json::EscapeString(update_info.supports_rollback) << R"(",)";
×
295
                        content << R"("StateDataStoreCount":)" << to_string(update_info.state_data_store_count)
×
296
                                        << R"(,)";
×
297
                        content << R"("HasDBSchemaUpdate":)"
298
                                        << string(update_info.has_db_schema_update ? "true" : "false");
×
299
                }
300
                content << "}";
×
301
        }
302
        content << "}";
×
303

304
        return std::move(*content.rdbuf()).str();
×
305
}
306

307
error::Error Context::SaveDeploymentStateData(kv_db::Transaction &txn, StateData &state_data) {
×
308
        // We do not currently support this being set to true. It is only relevant if upgrading from
309
        // a client older than 2.0. It can become relevant again later if we upgrade the schema
310
        // version. See also comment about state_data_key_uncommitted.
311
        AssertOrReturnError(!state_data.update_info.has_db_schema_update);
×
312

313
        if (state_data.update_info.state_data_store_count++ >= kMaxStateDataStoreCount) {
×
314
                return main_context::MakeError(
315
                        main_context::StateDataStoreCountExceededError,
316
                        "State looping detected, breaking out of loop");
×
317
        }
318

319
        string content = GenerateStateDataJson(state_data);
×
320

321
        // TODO: Handle commit stage here.
322
        string store_key;
×
323
        if (state_data.update_info.has_db_schema_update) {
×
324
                store_key = mender_context.state_data_key_uncommitted;
×
325
        } else {
326
                store_key = mender_context.state_data_key;
×
327
        }
328

329
        auto err = txn.Write(store_key, common::ByteVectorFromString(content));
×
330
        if (err != error::NoError) {
×
331
                return err.WithContext("Could not write state data");
×
332
        }
333

334
        return error::NoError;
×
335
}
336

337
error::Error Context::SaveDeploymentStateData(StateData &state_data) {
×
338
        auto &db = mender_context.GetMenderStoreDB();
×
339
        return db.WriteTransaction([this, &state_data](kv_db::Transaction &txn) {
×
340
                return SaveDeploymentStateData(txn, state_data);
×
341
        });
×
342
}
343

344
static error::Error UnmarshalJsonStateData(const json::Json &json, StateData &state_data) {
×
345
#define SetOrReturnIfError(dst, expr) \
346
        if (!expr) {                      \
347
                return expr.error();          \
348
        }                                 \
349
        dst = expr.value()
350

351
#define EmptyOrSetOrReturnIfError(dst, expr)                                   \
352
        if (!expr) {                                                               \
353
                if (expr.error().code == kv_db::MakeError(kv_db::KeyError, "").code) { \
354
                        dst.clear();                                                       \
355
                } else {                                                               \
356
                        return expr.error();                                               \
357
                }                                                                      \
358
        } else {                                                                   \
359
                dst = expr.value();                                                    \
360
        }
361

362
        auto exp_int = json.Get("Version").and_then(json::ToInt);
×
363
        SetOrReturnIfError(state_data.version, exp_int);
×
364

365
        if (state_data.version != kStateDataVersion) {
×
366
                return error::Error(
367
                        make_error_condition(errc::not_supported),
×
368
                        "State Data version not supported by this client");
×
369
        }
370

371
        auto exp_string = json.Get("Name").and_then(json::ToString);
×
372
        SetOrReturnIfError(state_data.state, exp_string);
×
373

374
        const auto &exp_json_update_info = json.Get("UpdateInfo");
×
375
        SetOrReturnIfError(const auto &json_update_info, exp_json_update_info);
×
376

377
        const auto &exp_json_artifact = json_update_info.Get("Artifact");
×
378
        SetOrReturnIfError(const auto &json_artifact, exp_json_artifact);
×
379

380
        const auto &exp_json_source = json_artifact.Get("Source");
×
381
        SetOrReturnIfError(const auto &json_source, exp_json_source);
×
382

383
        auto &update_info = state_data.update_info;
×
384
        auto &artifact = update_info.artifact;
×
385
        auto &source = artifact.source;
×
386

387
        exp_string = json_source.Get("URI").and_then(json::ToString);
×
388
        SetOrReturnIfError(source.uri, exp_string);
×
389

390
        exp_string = json_source.Get("Expire").and_then(json::ToString);
×
391
        SetOrReturnIfError(source.expire, exp_string);
×
392

393
        auto exp_string_vector = json_artifact.Get("CompatibleDevices").and_then(json::ToStringVector);
×
394
        SetOrReturnIfError(artifact.compatible_devices, exp_string_vector);
×
395

396
        exp_string_vector = json_artifact.Get("PayloadTypes").and_then(json::ToStringVector);
×
397
        SetOrReturnIfError(artifact.payload_types, exp_string_vector);
×
398

399
        exp_string = json_artifact.Get("ArtifactName").and_then(json::ToString);
×
400
        SetOrReturnIfError(artifact.artifact_name, exp_string);
×
401

402
        exp_string = json_artifact.Get("ArtifactGroup").and_then(json::ToString);
×
403
        SetOrReturnIfError(artifact.artifact_group, exp_string);
×
404

405
        auto exp_string_map = json_artifact.Get("TypeInfoProvides").and_then(json::ToKeyValuesMap);
×
406
        EmptyOrSetOrReturnIfError(artifact.type_info_provides, exp_string_map);
×
407

408
        exp_string_vector = json_artifact.Get("ClearsArtifactProvides").and_then(json::ToStringVector);
×
409
        EmptyOrSetOrReturnIfError(artifact.clears_artifact_provides, exp_string_vector);
×
410

411
        exp_string = json_update_info.Get("ID").and_then(json::ToString);
×
412
        SetOrReturnIfError(update_info.id, exp_string);
×
413

414
        exp_string_vector = json_update_info.Get("RebootRequested").and_then(json::ToStringVector);
×
415
        SetOrReturnIfError(update_info.reboot_requested, exp_string_vector);
×
416

417
        auto exp_bool = json_update_info.Get("SupportsRollback").and_then(json::ToBool);
×
418
        SetOrReturnIfError(update_info.supports_rollback, exp_bool);
×
419

420
        exp_int = json_update_info.Get("StateDataStoreCount").and_then(json::ToInt);
×
421
        SetOrReturnIfError(update_info.state_data_store_count, exp_int);
×
422

423
        exp_bool = json_update_info.Get("HasDBSchemaUpdate").and_then(json::ToBool);
×
424
        SetOrReturnIfError(update_info.has_db_schema_update, exp_bool);
×
425

426
#undef SetOrReturnIfError
427
#undef EmptyOrSetOrReturnIfError
428

429
        return error::NoError;
×
430
}
431

432
expected::ExpectedBool Context::LoadDeploymentStateData(StateData &state_data) {
×
433
        auto &db = mender_context.GetMenderStoreDB();
×
434
        auto err = db.WriteTransaction([this, &state_data](kv_db::Transaction &txn) {
×
435
                auto exp_content = txn.Read(mender_context.state_data_key);
×
436
                if (!exp_content) {
×
437
                        return exp_content.error().WithContext("Could not load state data");
×
438
                }
439
                auto &content = exp_content.value();
×
440

441
                auto exp_json = json::Load(common::StringFromByteVector(content));
×
442
                if (!exp_json) {
×
443
                        return exp_json.error().WithContext("Could not load state data");
×
444
                }
445

446
                auto err = UnmarshalJsonStateData(exp_json.value(), state_data);
×
447
                if (err != error::NoError) {
×
448
                        if (err.code != make_error_condition(errc::not_supported)) {
×
449
                                return err.WithContext("Could not load state data");
×
450
                        }
451

452
                        // Try again with the state_data_key_uncommitted.
453
                        exp_content = txn.Read(mender_context.state_data_key_uncommitted);
×
454
                        if (!exp_content) {
×
455
                                return err.WithContext("Could not load state data").FollowedBy(exp_content.error());
×
456
                        }
457
                        auto &content = exp_content.value();
×
458

459
                        exp_json = json::Load(common::StringFromByteVector(content));
×
460
                        if (!exp_json) {
×
461
                                return err.WithContext("Could not load state data").FollowedBy(exp_json.error());
×
462
                        }
463

464
                        auto inner_err = UnmarshalJsonStateData(exp_json.value(), state_data);
×
465
                        if (inner_err != error::NoError) {
×
466
                                return err.WithContext("Could not load state data").FollowedBy(inner_err);
×
467
                        }
468
                }
469

470
                // Every load also saves, which increments the state_data_store_count.
471
                return SaveDeploymentStateData(txn, state_data);
×
472
        });
×
473

474
        if (err == error::NoError) {
×
475
                return true;
×
476
        } else if (err.code == kv_db::MakeError(kv_db::KeyError, "").code) {
×
477
                return false;
×
478
        } else {
479
                return expected::unexpected(err);
×
480
        }
481
}
482

483
} // namespace daemon
484
} // namespace update
485
} // 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