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

mendersoftware / mender / 2058950510

24 Sep 2025 07:56AM UTC coverage: 79.863% (+3.9%) from 75.919%
2058950510

push

gitlab-ci

web-flow
Merge pull request #1827 from lluiscampos/QA-1223-longer-timeouts-http

QA-1223: Add longer timeouts for HTTP tests reaching external services

7801 of 9768 relevant lines covered (79.86%)

14045.84 hits per line

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

84.0
/src/mender-update/context/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/context.hpp>
16

17
#include <cctype>
18

19
#include <algorithm>
20
#include <set>
21

22
#include <artifact/artifact.hpp>
23
#include <common/common.hpp>
24
#include <common/error.hpp>
25
#include <common/expected.hpp>
26
#include <common/io.hpp>
27
#include <common/json.hpp>
28
#include <common/key_value_database.hpp>
29
#include <common/log.hpp>
30
#include <common/path.hpp>
31

32
#ifdef MENDER_USE_YAML_CPP
33
#include <common/yaml.hpp>
34
#endif // MENDER_USE_YAML_CPP
35

36
namespace mender {
37
namespace update {
38
namespace context {
39

40
using namespace std;
41
namespace artifact = mender::artifact;
42
namespace common = mender::common;
43
namespace error = mender::common::error;
44
namespace expected = mender::common::expected;
45
namespace io = mender::common::io;
46
namespace json = mender::common::json;
47
namespace kv_db = mender::common::key_value_database;
48
namespace log = mender::common::log;
49
namespace path = mender::common::path;
50

51
#ifdef MENDER_USE_YAML_CPP
52
namespace yaml = mender::common::yaml;
53
#endif // MENDER_USE_YAML_CPP
54

55
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
56
const string MenderContext::orchestrator_manifest_payload_type {"mender-orchestrator-manifest"};
57

58
const string MenderContext::artifact_name_key {"artifact-name"};
59
const string MenderContext::artifact_group_key {"artifact-group"};
60
const string MenderContext::artifact_provides_key {"artifact-provides"};
61
const string MenderContext::standalone_state_key {"standalone-state"};
62
const string MenderContext::state_data_key {"state"};
63
const string MenderContext::state_data_key_uncommitted {"state-uncommitted"};
64
const string MenderContext::update_control_maps {"update-control-maps"};
65
const string MenderContext::auth_token_name {"authtoken"};
66
const string MenderContext::auth_token_cache_invalidator_name {"auth-token-cache-invalidator"};
67

68
const int MenderContext::standalone_data_version {2};
69

70
const MenderContextErrorCategoryClass MenderContextErrorCategory;
71

72
const char *MenderContextErrorCategoryClass::name() const noexcept {
×
73
        return "MenderContextErrorCategory";
×
74
}
75

76
string MenderContextErrorCategoryClass::message(int code) const {
3✔
77
        switch (code) {
3✔
78
        case NoError:
79
                return "Success";
×
80
        case ParseError:
81
                return "Parse error";
×
82
        case ValueError:
83
                return "Value error";
×
84
        case NoSuchUpdateModuleError:
85
                return "Update Module not found for given artifact type";
×
86
        case DatabaseValueError:
87
                return "Value in database is invalid or corrupted";
×
88
        case RebootRequiredError:
89
                return "Reboot required";
×
90
        case NoUpdateInProgressError:
91
                return "No update in progress";
2✔
92
        case UnexpectedHttpResponse:
93
                return "Unexpected HTTP response";
×
94
        case StateDataStoreCountExceededError:
95
                return "State data store count exceeded";
1✔
96
        case WrongOperationError:
97
                return "Operation cannot be done in this state";
×
98
        }
99
        assert(false);
100
        return "Unknown";
×
101
}
102

103
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
376✔
104
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
382✔
105
}
106

107
error::Error MenderContext::Initialize() {
441✔
108
#ifdef MENDER_USE_LMDB
109
        auto err = mender_store_.Open(path::Join(config_.paths.GetDataStore(), "mender-store"));
882✔
110
        if (error::NoError != err) {
441✔
111
                return err;
1✔
112
        }
113
        err = mender_store_.Remove(auth_token_name);
440✔
114
        if (error::NoError != err) {
440✔
115
                // key not existing in the DB is not treated as an error so this must be
116
                // a real error
117
                return err;
×
118
        }
119
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
440✔
120
        if (error::NoError != err) {
440✔
121
                // same as above -- a real error
122
                return err;
×
123
        }
124

125
        return error::NoError;
440✔
126
#else
127
        return error::NoError;
128
#endif
129
}
130

131
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
1,738✔
132
        return mender_store_;
1,738✔
133
}
134

135
ExpectedProvidesData MenderContext::LoadProvides() {
415✔
136
        ExpectedProvidesData data;
137
        auto err = mender_store_.ReadTransaction([this, &data](kv_db::Transaction &txn) {
414✔
138
                data = LoadProvides(txn);
828✔
139
                if (!data) {
414✔
140
                        return data.error();
2✔
141
                }
142
                return error::NoError;
412✔
143
        });
415✔
144
        if (err != error::NoError) {
415✔
145
                return expected::unexpected(err);
6✔
146
        }
147
        return data;
148
}
149

150
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
508✔
151
        string artifact_name;
152
        string artifact_group;
153
        string artifact_provides_str;
154

155
        auto err = kv_db::ReadString(txn, artifact_name_key, artifact_name, true);
508✔
156
        if (err != error::NoError) {
508✔
157
                return expected::unexpected(err);
×
158
        }
159
        err = kv_db::ReadString(txn, artifact_group_key, artifact_group, true);
508✔
160
        if (err != error::NoError) {
508✔
161
                return expected::unexpected(err);
×
162
        }
163
        err = kv_db::ReadString(txn, artifact_provides_key, artifact_provides_str, true);
508✔
164
        if (err != error::NoError) {
508✔
165
                return expected::unexpected(err);
×
166
        }
167

168
        ProvidesData ret {};
508✔
169
        if (artifact_name != "") {
508✔
170
                ret["artifact_name"] = artifact_name;
1,000✔
171
        }
172
        if (artifact_group != "") {
508✔
173
                ret["artifact_group"] = artifact_group;
20✔
174
        }
175
        if (artifact_provides_str == "") {
508✔
176
                // nothing more to do
177
                return ret;
388✔
178
        }
179

180
        auto ex_j = json::Load(artifact_provides_str);
240✔
181
        if (!ex_j) {
120✔
182
                return expected::unexpected(ex_j.error());
2✔
183
        }
184
        auto ex_children = ex_j.value().GetChildren();
119✔
185
        if (!ex_children) {
119✔
186
                return expected::unexpected(ex_children.error());
×
187
        }
188

189
        auto children = ex_children.value();
119✔
190
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
119✔
191
                        return it.second.IsString();
213✔
192
                })) {
193
                auto err = json::MakeError(json::TypeError, "Unexpected non-string data in provides");
2✔
194
                return expected::unexpected(err);
2✔
195
        }
196
        for (const auto &it : ex_children.value()) {
330✔
197
                ret[it.first] = it.second.GetString().value();
424✔
198
        }
199

200
        return ret;
118✔
201
}
202

203
#ifdef MENDER_USE_YAML_CPP
204
expected::ExpectedString MenderContext::GetSystemType() {
4✔
205
        string topology_dir =
206
                conf::GetEnv("MENDER_ORCHESTRATOR_TOPOLOGY_DIR", "/var/lib/mender-orchestrator/");
8✔
207
        string topology_file_path = path::Join(topology_dir, "topology.yaml");
4✔
208

209
        auto ex_topology = yaml::LoadFromFile(topology_file_path);
8✔
210
        if (!ex_topology) {
4✔
211
                return expected::unexpected(MakeError(
1✔
212
                        ParseError,
213
                        "Failed to load topology file '" + topology_file_path
2✔
214
                                + "': " + ex_topology.error().message));
3✔
215
        }
216

217
        auto ex_system_type = ex_topology.value().Get("system_type");
3✔
218
        if (!ex_system_type) {
3✔
219
                return expected::unexpected(MakeError(
×
220
                        ParseError,
221
                        "Failed to find 'system_type' in topology file '" + topology_file_path
×
222
                                + "': " + ex_system_type.error().message));
×
223
        }
224

225
        auto ex_system_type_str = ex_system_type.value().Get<string>();
3✔
226
        if (!ex_system_type_str) {
3✔
227
                return expected::unexpected(MakeError(
×
228
                        ParseError,
229
                        "Failed to parse 'system_type' as string in topology file '" + topology_file_path
×
230
                                + "': " + ex_system_type_str.error().message));
×
231
        }
232

233
        return ex_system_type_str.value();
3✔
234
}
235
#endif // MENDER_USE_YAML_CPP
236

237
expected::ExpectedString MenderContext::GetDeviceType() {
297✔
238
        string device_type_fpath;
239
        if (config_.device_type_file != "") {
297✔
240
                device_type_fpath = config_.device_type_file;
3✔
241
        } else {
242
                device_type_fpath = path::Join(config_.paths.GetDataStore(), "device_type");
588✔
243
        }
244
        auto ex_is = io::OpenIfstream(device_type_fpath);
297✔
245
        if (!ex_is) {
297✔
246
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
12✔
247
        }
248

249
        auto &is = ex_is.value();
291✔
250
        string line;
251
        errno = 0;
291✔
252
        getline(is, line);
291✔
253
        if (is.bad()) {
291✔
254
                int io_errno = errno;
×
255
                error::Error err {
256
                        generic_category().default_error_condition(io_errno),
×
257
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
258
                return expected::ExpectedString(expected::unexpected(err));
×
259
        }
260

261
        const string::size_type eq_pos = 12;
262
        if (line.substr(0, eq_pos) != "device_type=") {
582✔
263
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
264
                return expected::ExpectedString(expected::unexpected(err));
6✔
265
        }
266

267
        string ret = line.substr(eq_pos, string::npos);
288✔
268

269
        if (!is.eof()) {
288✔
270
                errno = 0;
287✔
271
                getline(is, line);
287✔
272
                if ((line != "") || (!is.eof())) {
287✔
273
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
274
                        return expected::ExpectedString(expected::unexpected(err));
4✔
275
                }
276
        }
277

278
        return expected::ExpectedString(ret);
286✔
279
}
280

281
// This function determines whether we return the system_type from the topology
282
// or the device_type. The system_type should be used rather than the device_type in two places:
283
//   1) When we poll for a deployment and the device_tier is set to `system`
284
//   2) When matching the artifact context when installing a mender-orchestrator manifest
285
expected::ExpectedString MenderContext::GetCompatibleType(const string &payload_type) {
125✔
286
        if (config_.device_tier == "system") {
125✔
287
#ifdef MENDER_USE_YAML_CPP
288
                if (payload_type == "") {
4✔
289
                        // Deployment polling -> use system_type
290
                        return GetSystemType();
2✔
291
                } else if (payload_type == orchestrator_manifest_payload_type) {
2✔
292
                        // Manifest installtion -> use system_type
293
                        return GetSystemType();
1✔
294
                } else {
295
                        // Regular artifact processing (updated in standalone mode) -> use device_type
296
                        return GetDeviceType();
1✔
297
                }
298
#else
299
                return expected::unexpected(MakeError(
300
                        ValueError, "DeviceTier is 'system', but MENDER_USE_YAML_CPP is not enabled"));
301
#endif // MENDER_USE_YAML_CPP
302
        }
303

304
        // Regular device, always use device_type
305
        return GetDeviceType();
121✔
306
}
307

308
bool CheckClearsMatch(const string &to_match, const string &clears_string) {
275✔
309
        if (clears_string.empty()) {
275✔
310
                return to_match.empty();
10✔
311
        }
312

313
        vector<std::string> sub_strings;
265✔
314
        string escaped;
315
        for (const auto chr : clears_string) {
4,342✔
316
                if (chr == '*') {
4,077✔
317
                        sub_strings.push_back(escaped);
144✔
318
                        escaped.clear();
319
                } else {
320
                        escaped.push_back(chr);
3,933✔
321
                }
322
        }
323
        sub_strings.push_back(escaped);
265✔
324

325
        // Make sure that that front of vector starts at index 0
326
        if (sub_strings.front() != ""
265✔
327
                && to_match.compare(0, sub_strings.front().size(), sub_strings.front()) != 0) {
265✔
328
                return false;
329
        }
330
        // Checks if no trailing wildcard
331
        if (sub_strings.back() != ""
58✔
332
                && to_match.compare(
58✔
333
                           to_match.size() - sub_strings.back().size(), to_match.size(), sub_strings.back())
334
                           != 0) {
335
                return false;
336
        }
337

338
        // Iterate over substrings, set boundary if found to avoid
339
        // matching same substring twice
340
        size_t boundary = 0;
341
        for (const auto &str : sub_strings) {
156✔
342
                if (!str.empty()) {
109✔
343
                        size_t find = to_match.find(str, boundary);
57✔
344
                        if (find == string::npos) {
57✔
345
                                return false;
346
                        }
347
                        boundary = find + str.size();
55✔
348
                }
349
        }
350
        return true;
351
}
352

353
error::Error FilterProvides(
93✔
354
        const ProvidesData &new_provides,
355
        const ClearsProvidesData &clears_provides,
356
        ProvidesData &to_modify) {
357
        // Use clears_provides to filter out unwanted provides.
358
        for (auto to_clear : clears_provides) {
257✔
359
                set<string> keys;
360
                for (auto provide : to_modify) {
658✔
361
                        if (CheckClearsMatch(provide.first, to_clear)) {
247✔
362
                                keys.insert(provide.first);
32✔
363
                        }
364
                }
365
                for (auto key : keys) {
196✔
366
                        to_modify.erase(key);
32✔
367
                }
368
        }
369

370
        // Now add the provides from the new_provides set.
371
        for (auto provide : new_provides) {
335✔
372
                to_modify[provide.first] = provide.second;
373
        }
374

375
        return error::NoError;
93✔
376
}
377

378
error::Error MenderContext::CommitArtifactData(
94✔
379
        string artifact_name,
380
        string artifact_group,
381
        const optional<ProvidesData> &new_provides,
382
        const optional<ClearsProvidesData> &clears_provides,
383
        function<error::Error(kv_db::Transaction &)> txn_func) {
384
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
94✔
385
                auto exp_existing = LoadProvides(txn);
94✔
386
                if (!exp_existing) {
94✔
387
                        return exp_existing.error();
×
388
                }
389
                auto modified_provides = exp_existing.value();
94✔
390

391
                error::Error err;
94✔
392
                if (!new_provides && !clears_provides) {
161✔
393
                        // Neither provides nor clear_provides came with the artifact. This means
394
                        // erase everything. `artifact_name` and `artifact_group` will still be
395
                        // preserved through special cases below.
396
                        modified_provides.clear();
397
                } else if (!new_provides) {
88✔
398
                        // No new provides came with the artifact. This means filter what we have,
399
                        // but don't add any new provides fields.
400
                        ProvidesData empty_provides;
401
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
42✔
402
                } else if (!clears_provides) {
67✔
403
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
404
                        modified_provides = new_provides.value();
3✔
405
                } else {
406
                        // Standard case, filter existing provides using clears_provides, and then
407
                        // add new ones on top.
408
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
128✔
409
                }
410
                if (err != error::NoError) {
94✔
411
                        return err;
×
412
                }
413

414
                if (artifact_name != "") {
94✔
415
                        modified_provides["artifact_name"] = artifact_name;
188✔
416
                }
417
                if (artifact_group != "") {
94✔
418
                        modified_provides["artifact_group"] = artifact_group;
8✔
419
                }
420

421
                string artifact_provides_str {"{"};
94✔
422
                for (const auto &it : modified_provides) {
308✔
423
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
214✔
424
                                artifact_provides_str += "\"" + json::EscapeString(it.first) + "\":" + "\""
230✔
425
                                                                                 + json::EscapeString(it.second) + "\",";
460✔
426
                        }
427
                }
428

429
                // if some key-value pairs were added, replace the trailing comma with the
430
                // closing '}' to make a valid JSON
431
                if (artifact_provides_str != "{") {
94✔
432
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
68✔
433
                } else {
434
                        // set to an empty value for consistency with the other two items
435
                        artifact_provides_str = "";
26✔
436
                }
437

438
                if (modified_provides["artifact_name"] != "") {
282✔
439
                        err = txn.Write(
94✔
440
                                artifact_name_key,
441
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
282✔
442
                        if (err != error::NoError) {
94✔
443
                                return err;
×
444
                        }
445
                } else {
446
                        // This should not happen.
447
                        AssertOrReturnError(false);
×
448
                }
449

450
                if (modified_provides["artifact_group"] != "") {
282✔
451
                        err = txn.Write(
5✔
452
                                artifact_group_key,
453
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
15✔
454
                } else {
455
                        err = txn.Remove(artifact_group_key);
178✔
456
                }
457
                if (err != error::NoError) {
94✔
458
                        return err;
×
459
                }
460

461
                if (artifact_provides_str != "") {
94✔
462
                        err = txn.Write(
68✔
463
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
68✔
464
                        if (err != error::NoError) {
68✔
465
                                return err;
×
466
                        }
467
                }
468
                return txn_func(txn);
94✔
469
        });
188✔
470
}
471

472
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
111✔
473
        auto ex_compatible_type = GetCompatibleType(hdr_view.type_info.type);
111✔
474
        if (!ex_compatible_type) {
111✔
475
                return expected::unexpected(ex_compatible_type.error());
4✔
476
        }
477
        auto &compatible_type = ex_compatible_type.value();
109✔
478

479
        auto ex_provides = LoadProvides();
109✔
480
        if (!ex_provides) {
109✔
481
                return expected::unexpected(ex_provides.error());
×
482
        }
483
        auto &provides = ex_provides.value();
109✔
484
        return ArtifactMatchesContext(provides, compatible_type, hdr_view);
109✔
485
}
486

487
expected::ExpectedBool ArtifactMatchesContext(
124✔
488
        const ProvidesData &provides,
489
        const string &compatible_type,
490
        const artifact::HeaderView &hdr_view) {
491
        using common::MapContainsStringKey;
492
        if (!MapContainsStringKey(provides, "artifact_name")) {
248✔
493
                return expected::unexpected(
×
494
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
495
        }
496

497
        auto hdr_depends = hdr_view.GetDepends();
124✔
498
        AssertOrReturnUnexpected(hdr_depends["device_type"].size() > 0);
250✔
499
        if (!common::VectorContainsString(hdr_depends["device_type"], compatible_type)) {
369✔
500
                log::Error("Artifact device type doesn't match");
4✔
501
                return false;
502
        }
503
        hdr_depends.erase("device_type");
242✔
504

505
        AssertOrReturnUnexpected(
155✔
506
                !MapContainsStringKey(hdr_depends, "artifact_name")
507
                || (hdr_depends["artifact_name"].size() > 0));
508

509
        AssertOrReturnUnexpected(
151✔
510
                !MapContainsStringKey(hdr_depends, "artifact_group")
511
                || (hdr_depends["artifact_group"].size() > 0));
512

513
        for (auto it : hdr_depends) {
141✔
514
                if (!common::MapContainsStringKey(provides, it.first)) {
17✔
515
                        log::Error("Missing '" + it.first + "' in provides, required by artifact depends");
4✔
516
                        return false;
517
                }
518
                if (!common::VectorContainsString(hdr_depends[it.first], provides.at(it.first))) {
15✔
519
                        log::Error(
4✔
520
                                "Provides value '" + provides.at(it.first) + "' doesn't match any of the '"
8✔
521
                                + it.first + "' artifact depends ("
8✔
522
                                + common::StringVectorToString(hdr_depends[it.first]) + ")");
20✔
523
                        return false;
524
                }
525
        }
526

527
        return true;
528
}
529

530
} // namespace context
531
} // namespace update
532
} // 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