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

mendersoftware / mender / 2291732227

28 Jan 2026 01:31PM UTC coverage: 81.749% (+0.2%) from 81.518%
2291732227

push

gitlab-ci

web-flow
Merge pull request #1891 from michalkopczan/MEN-8850-graceful-handling-of-http-429-full

These are the remaining changes for HTTP 429 handling (MEN-8850) - inventory polling and pushing status/logs.

130 of 164 new or added lines in 3 files covered. (79.27%)

1 existing line in 1 file now uncovered.

8936 of 10931 relevant lines covered (81.75%)

20083.05 hits per line

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

80.07
/src/mender-update/deployments/deployments.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/deployments.hpp>
16

17
#include <algorithm>
18
#include <sstream>
19
#include <string>
20

21
#include <api/api.hpp>
22
#include <api/client.hpp>
23
#include <common/common.hpp>
24
#include <common/error.hpp>
25
#include <common/events.hpp>
26
#include <common/expected.hpp>
27
#include <common/http.hpp>
28
#include <common/io.hpp>
29
#include <common/json.hpp>
30
#include <common/log.hpp>
31
#include <common/optional.hpp>
32
#include <mender-update/context.hpp>
33

34
namespace mender {
35
namespace update {
36
namespace deployments {
37

38
using namespace std;
39

40
namespace api = mender::api;
41
namespace common = mender::common;
42
namespace context = mender::update::context;
43
namespace error = mender::common::error;
44
namespace events = mender::common::events;
45
namespace expected = mender::common::expected;
46
namespace http = mender::common::http;
47
namespace io = mender::common::io;
48
namespace json = mender::common::json;
49
namespace log = mender::common::log;
50

51
const DeploymentsErrorCategoryClass DeploymentsErrorCategory;
52

53
const char *DeploymentsErrorCategoryClass::name() const noexcept {
×
54
        return "DeploymentsErrorCategory";
×
55
}
56

57
string DeploymentsErrorCategoryClass::message(int code) const {
33✔
58
        switch (code) {
33✔
59
        case NoError:
60
                return "Success";
×
61
        case InvalidDataError:
62
                return "Invalid data error";
×
63
        case BadResponseError:
64
                return "Bad response error";
4✔
65
        case DeploymentAbortedError:
66
                return "Deployment was aborted on the server";
3✔
67
        case TooManyRequestsError:
68
                return "Too many requests";
26✔
69
        }
70
        assert(false);
71
        return "Unknown";
×
72
}
73

74
error::Error MakeError(DeploymentsErrorCode code, const string &msg) {
52✔
75
        return error::Error(error_condition(code, DeploymentsErrorCategory), msg);
66✔
76
}
77

78
static const string check_updates_v1_uri = "/api/devices/v1/deployments/device/deployments/next";
79
static const string check_updates_v2_uri = "/api/devices/v2/deployments/device/deployments/next";
80

81
error::Error DeploymentClient::CheckNewDeployments(
10✔
82
        context::MenderContext &ctx, api::Client &client, CheckUpdatesAPIResponseHandler api_handler) {
83
        auto ex_compatible_type = ctx.GetCompatibleType();
20✔
84
        if (!ex_compatible_type) {
10✔
85
                return ex_compatible_type.error();
4✔
86
        }
87
        string compatible_type = ex_compatible_type.value();
6✔
88

89
        auto ex_provides = ctx.LoadProvides();
6✔
90
        if (!ex_provides) {
6✔
91
                return ex_provides.error();
×
92
        }
93
        auto provides = ex_provides.value();
6✔
94
        if (provides.find("artifact_name") == provides.end()) {
12✔
95
                return MakeError(InvalidDataError, "Missing artifact name data");
×
96
        }
97

98
        stringstream ss;
6✔
99
        ss << R"({"device_provides":{)";
6✔
100
        ss << R"("device_type":")";
6✔
101
        ss << json::EscapeString(compatible_type);
12✔
102

103
        for (const auto &kv : provides) {
14✔
104
                ss << "\",\"" + json::EscapeString(kv.first) + "\":\"";
8✔
105
                ss << json::EscapeString(kv.second);
16✔
106
        }
107

108
        ss << R"("}})";
6✔
109

110
        string v2_payload = ss.str();
111
        log::Debug("deployments/next v2 payload " + v2_payload);
6✔
112
        http::BodyGenerator payload_gen = [v2_payload]() {
54✔
113
                return make_shared<io::StringReader>(v2_payload);
6✔
114
        };
6✔
115

116
        auto v2_req = make_shared<api::APIRequest>();
6✔
117
        v2_req->SetPath(check_updates_v2_uri);
118
        v2_req->SetMethod(http::Method::POST);
6✔
119
        v2_req->SetHeader("Content-Type", "application/json");
12✔
120
        v2_req->SetHeader("Content-Length", to_string(v2_payload.size()));
12✔
121
        v2_req->SetHeader("Accept", "application/json");
12✔
122
        v2_req->SetBodyGenerator(payload_gen);
6✔
123

124
        string v1_args = "artifact_name=" + http::URLEncode(provides["artifact_name"])
12✔
125
                                         + "&device_type=" + http::URLEncode(compatible_type);
18✔
126
        auto v1_req = make_shared<api::APIRequest>();
6✔
127
        v1_req->SetPath(check_updates_v1_uri + "?" + v1_args);
6✔
128
        v1_req->SetMethod(http::Method::GET);
6✔
129
        v1_req->SetHeader("Accept", "application/json");
12✔
130

131
        auto received_body = make_shared<vector<uint8_t>>();
6✔
132
        auto handle_data = [received_body, api_handler](unsigned status) {
4✔
133
                if (status == http::StatusOK) {
4✔
134
                        auto ex_j = json::Load(common::StringFromByteVector(*received_body));
4✔
135
                        if (ex_j) {
2✔
136
                                CheckUpdatesAPIResponse response {optional<json::Json> {ex_j.value()}};
2✔
137
                                api_handler(response);
4✔
138
                        } else {
139
                                api_handler(expected::unexpected(
×
140
                                        CheckUpdatesAPIResponseError {status, nullopt, ex_j.error()}));
×
141
                        }
142
                } else if (status == http::StatusNoContent) {
2✔
143
                        api_handler(CheckUpdatesAPIResponse {nullopt});
4✔
144
                } else {
145
                        log::Warning(
×
146
                                "DeploymentClient::CheckNewDeployments - received unhandled http response: "
147
                                + to_string(status));
×
148
                        api_handler(expected::unexpected(CheckUpdatesAPIResponseError {
×
149
                                status,
150
                                nullopt,
151
                                MakeError(
152
                                        DeploymentAbortedError,
153
                                        "received unhandled HTTP response: " + to_string(status))}));
×
154
                }
155
        };
10✔
156

157
        http::ResponseHandler header_handler =
158
                [this, received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
12✔
159
                        this->HeaderHandler(received_body, api_handler, exp_resp);
27✔
160
                };
15✔
161

162
        http::ResponseHandler v1_body_handler =
163
                [received_body, api_handler, handle_data](http::ExpectedIncomingResponsePtr exp_resp) {
15✔
164
                        if (!exp_resp) {
3✔
165
                                log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
166
                                CheckUpdatesAPIResponse response = expected::unexpected(
×
167
                                        CheckUpdatesAPIResponseError {nullopt, nullopt, exp_resp.error()});
×
168
                                api_handler(response);
×
169
                                return;
170
                        }
171
                        auto resp = exp_resp.value();
3✔
172
                        auto status = resp->GetStatusCode();
3✔
173

174
                        // StatusTooManyRequests must have been handled in HeaderHandler already
175
                        assert(status != http::StatusTooManyRequests);
176

177
                        if ((status == http::StatusOK) || (status == http::StatusNoContent)) {
3✔
178
                                handle_data(status);
2✔
179
                        } else {
180
                                auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
1✔
181
                                string err_str;
182
                                if (ex_err_msg) {
1✔
183
                                        err_str = ex_err_msg.value();
×
184
                                } else {
185
                                        err_str = resp->GetStatusMessage();
2✔
186
                                }
187
                                api_handler(expected::unexpected(CheckUpdatesAPIResponseError {
3✔
188
                                        status,
189
                                        nullopt,
190
                                        MakeError(
191
                                                BadResponseError,
192
                                                "Got unexpected response " + to_string(status) + ": " + err_str)}));
2✔
193
                        }
194
                };
6✔
195

196
        http::ResponseHandler v2_body_handler = [received_body,
18✔
197
                                                                                         v1_req,
198
                                                                                         header_handler,
199
                                                                                         v1_body_handler,
200
                                                                                         api_handler,
201
                                                                                         handle_data,
202
                                                                                         &client](http::ExpectedIncomingResponsePtr exp_resp) {
203
                if (!exp_resp) {
6✔
204
                        log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
205
                        CheckUpdatesAPIResponse response = expected::unexpected(
×
206
                                CheckUpdatesAPIResponseError {nullopt, nullopt, exp_resp.error()});
×
207
                        api_handler(response);
×
208
                        return;
209
                }
210
                auto resp = exp_resp.value();
6✔
211
                auto status = resp->GetStatusCode();
6✔
212

213
                // StatusTooManyRequests must have been handled in HeaderHandler already
214
                assert(status != http::StatusTooManyRequests);
215

216
                if ((status == http::StatusOK) || (status == http::StatusNoContent)) {
6✔
217
                        handle_data(status);
2✔
218
                } else if (status == http::StatusNotFound) {
4✔
219
                        log::Debug(
3✔
220
                                "POST request to v2 version of the deployments API failed, falling back to v1 version and GET");
221
                        auto err = client.AsyncCall(v1_req, header_handler, v1_body_handler);
9✔
222
                        if (err != error::NoError) {
3✔
223
                                api_handler(expected::unexpected(CheckUpdatesAPIResponseError {
×
224
                                        status, nullopt, err.WithContext("While calling v1 endpoint")}));
×
225
                        }
226
                } else {
227
                        auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
1✔
228
                        string err_str;
229
                        if (ex_err_msg) {
1✔
230
                                err_str = ex_err_msg.value();
1✔
231
                        } else {
232
                                err_str = resp->GetStatusMessage();
×
233
                        }
234
                        api_handler(expected::unexpected(CheckUpdatesAPIResponseError {
3✔
235
                                status,
236
                                nullopt,
237
                                MakeError(
238
                                        BadResponseError,
239
                                        "Got unexpected response " + to_string(status) + ": " + err_str)}));
2✔
240
                }
241
        };
6✔
242

243
        return client.AsyncCall(v2_req, header_handler, v2_body_handler);
18✔
244
}
12✔
245

246
void DeploymentClient::HeaderHandler(
12✔
247
        shared_ptr<vector<uint8_t>> received_body,
248
        CheckUpdatesAPIResponseHandler api_handler,
249
        http::ExpectedIncomingResponsePtr exp_resp) {
250
        if (!exp_resp) {
12✔
251
                log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
252
                CheckUpdatesAPIResponse response =
253
                        expected::unexpected(CheckUpdatesAPIResponseError {nullopt, nullopt, exp_resp.error()});
×
254
                api_handler(response);
×
255
                return;
256
        }
257

258
        auto resp = exp_resp.value();
12✔
259
        auto status = resp->GetStatusCode();
12✔
260
        if (status == http::StatusTooManyRequests) {
12✔
261
                CheckUpdatesAPIResponse response = expected::unexpected(CheckUpdatesAPIResponseError {
6✔
262
                        status, resp->GetHeaders(), MakeError(TooManyRequestsError, "Too many requests")});
9✔
263
                api_handler(response);
6✔
264
                return;
265
        }
266
        received_body->clear();
9✔
267
        auto body_writer = make_shared<io::ByteWriter>(received_body);
9✔
268
        body_writer->SetUnlimited(true);
9✔
269
        resp->SetBodyWriter(body_writer);
18✔
270
}
271

272
static const string deployment_status_strings[static_cast<int>(DeploymentStatus::End_) + 1] = {
273
        "installing",
274
        "pause_before_installing",
275
        "downloading",
276
        "pause_before_rebooting",
277
        "rebooting",
278
        "pause_before_committing",
279
        "success",
280
        "failure",
281
        "already-installed"};
282

283
static const string deployments_uri_prefix = "/api/devices/v1/deployments/device/deployments";
284
static const string status_uri_suffix = "/status";
285

286
string DeploymentStatusString(DeploymentStatus status) {
501✔
287
        return deployment_status_strings[static_cast<int>(status)];
505✔
288
}
289

290
error::Error DeploymentClient::PushStatus(
4✔
291
        const string &deployment_id,
292
        DeploymentStatus status,
293
        const string &substate,
294
        api::Client &client,
295
        StatusAPIResponseHandler api_handler) {
296
        // Cannot push a status update without a deployment ID
297
        AssertOrReturnError(deployment_id != "");
4✔
298
        string payload = R"({"status":")" + DeploymentStatusString(status) + "\"";
4✔
299
        if (substate != "") {
4✔
300
                payload += R"(,"substate":")" + json::EscapeString(substate) + "\"}";
6✔
301
        } else {
302
                payload += "}";
1✔
303
        }
304
        http::BodyGenerator payload_gen = [payload]() {
36✔
305
                return make_shared<io::StringReader>(payload);
4✔
306
        };
4✔
307

308
        auto req = make_shared<api::APIRequest>();
4✔
309
        req->SetPath(http::JoinUrl(deployments_uri_prefix, deployment_id, status_uri_suffix));
4✔
310
        req->SetMethod(http::Method::PUT);
4✔
311
        req->SetHeader("Content-Type", "application/json");
8✔
312
        req->SetHeader("Content-Length", to_string(payload.size()));
8✔
313
        req->SetHeader("Accept", "application/json");
8✔
314
        req->SetBodyGenerator(payload_gen);
4✔
315

316
        auto received_body = make_shared<vector<uint8_t>>();
4✔
317
        return client.AsyncCall(
16✔
318
                req,
319
                [this, received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
8✔
320
                        this->PushStatusHeaderHandler(received_body, api_handler, exp_resp);
12✔
321
                },
4✔
322
                [received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
12✔
323
                        if (!exp_resp) {
4✔
324
                                log::Error("Request to push status data failed: " + exp_resp.error().message);
×
NEW
325
                                api_handler(StatusAPIResponse {nullopt, nullopt, exp_resp.error()});
×
326
                                return;
×
327
                        }
328

329
                        auto resp = exp_resp.value();
4✔
330
                        auto status = resp->GetStatusCode();
4✔
331

332
                        // StatusTooManyRequests must have been handled in PushStatusHeaderHandler already
333
                        assert(status != http::StatusTooManyRequests);
334

335
                        if (status == http::StatusNoContent) {
4✔
336
                                api_handler(StatusAPIResponse {status, nullopt, error::NoError});
2✔
337
                        } else if (status == http::StatusConflict) {
2✔
338
                                api_handler(StatusAPIResponse {
2✔
339
                                        status,
340
                                        nullopt,
341
                                        MakeError(DeploymentAbortedError, "Could not send status update to server")});
2✔
342
                        } else {
343
                                auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
1✔
344
                                string err_str;
345
                                if (ex_err_msg) {
1✔
346
                                        err_str = ex_err_msg.value();
1✔
347
                                } else {
348
                                        err_str = resp->GetStatusMessage();
×
349
                                }
350
                                api_handler(StatusAPIResponse {
2✔
351
                                        status,
352
                                        nullopt,
353
                                        MakeError(
354
                                                BadResponseError,
355
                                                "Got unexpected response " + to_string(status)
1✔
356
                                                        + " from status API: " + err_str)});
2✔
357
                        }
358
                });
4✔
359
}
360

361
void DeploymentClient::PushStatusHeaderHandler(
7✔
362
        shared_ptr<vector<uint8_t>> received_body,
363
        StatusAPIResponseHandler api_handler,
364
        http::ExpectedIncomingResponsePtr exp_resp) {
365
        if (!exp_resp) {
7✔
NEW
366
                log::Error("Request to push status data failed: " + exp_resp.error().message);
×
NEW
367
                api_handler(StatusAPIResponse {nullopt, nullopt, exp_resp.error()});
×
368
                return;
3✔
369
        }
370

371
        auto body_writer = make_shared<io::ByteWriter>(received_body);
7✔
372
        auto resp = exp_resp.value();
7✔
373
        auto status = resp->GetStatusCode();
7✔
374
        if (status == http::StatusTooManyRequests) {
7✔
375
                StatusAPIResponse response = {
376
                        status, resp->GetHeaders(), MakeError(TooManyRequestsError, "Too many requests")};
3✔
377
                api_handler(response);
3✔
378
                return;
379
        }
3✔
380
        auto content_length = resp->GetHeader("Content-Length");
8✔
381
        if (!content_length) {
4✔
NEW
382
                log::Debug(
×
383
                        "Failed to get content length from the status API response headers: "
NEW
384
                        + content_length.error().String());
×
NEW
385
                body_writer->SetUnlimited(true);
×
386
        } else {
387
                auto ex_len = common::StringTo<size_t>(content_length.value());
4✔
388
                if (!ex_len) {
4✔
NEW
389
                        log::Error(
×
390
                                "Failed to convert the content length from the status API response headers to an integer: "
NEW
391
                                + ex_len.error().String());
×
NEW
392
                        body_writer->SetUnlimited(true);
×
393
                } else {
394
                        received_body->resize(ex_len.value());
4✔
395
                }
396
        }
397
        resp->SetBodyWriter(body_writer);
8✔
398
}
399

400
using mender::common::expected::ExpectedSize;
401

402
static ExpectedSize GetLogFileDataSize(const string &path) {
3✔
403
        auto ex_istr = io::OpenIfstream(path);
3✔
404
        if (!ex_istr) {
3✔
405
                return expected::unexpected(ex_istr.error());
×
406
        }
407
        auto istr = std::move(ex_istr.value());
3✔
408

409
        // We want the size of the actual data without a potential trailing
410
        // newline. So let's seek one byte before the end of file, check if the last
411
        // byte is a newline and return the appropriate number.
412
        istr.seekg(-1, ios_base::end);
3✔
413
        int c = istr.get();
3✔
414
        if (c == '\n') {
3✔
415
                return istr.tellg() - static_cast<ifstream::off_type>(1);
3✔
416
        } else {
417
                return istr.tellg();
×
418
        }
419
}
3✔
420

421
const vector<uint8_t> JsonLogMessagesReader::header_ = {
422
        '{', '"', 'm', 'e', 's', 's', 'a', 'g', 'e', 's', '"', ':', '['};
423
const vector<uint8_t> JsonLogMessagesReader::closing_ = {']', '}'};
424

425
ExpectedSize JsonLogMessagesReader::Read(
89✔
426
        vector<uint8_t>::iterator start, vector<uint8_t>::iterator end) {
427
        if (header_rem_ > 0) {
89✔
428
                io::Vsize target_size = end - start;
9✔
429
                auto copy_end = copy_n(
9✔
430
                        header_.begin() + (header_.size() - header_rem_), min(header_rem_, target_size), start);
9✔
431
                auto n_copied = copy_end - start;
432
                header_rem_ -= n_copied;
9✔
433
                return static_cast<size_t>(n_copied);
434
        } else if (rem_raw_data_size_ > 0) {
80✔
435
                if (end - start > rem_raw_data_size_) {
64✔
436
                        end = start + static_cast<size_t>(rem_raw_data_size_);
437
                }
438
                auto ex_sz = reader_->Read(start, end);
64✔
439
                if (!ex_sz) {
64✔
440
                        return ex_sz;
441
                }
442
                auto n_read = ex_sz.value();
64✔
443
                rem_raw_data_size_ -= n_read;
64✔
444

445
                // We control how much we read from the file so we should never read
446
                // 0 bytes (meaning EOF reached). If we do, it means the file is
447
                // smaller than what we were told.
448
                assert(n_read > 0);
449
                if (n_read == 0) {
64✔
450
                        return expected::unexpected(
×
451
                                MakeError(InvalidDataError, "Unexpected EOF when reading logs file"));
×
452
                }
453

454
                // Replace all newlines with commas
455
                const auto read_end = start + n_read;
456
                for (auto it = start; it < read_end; it++) {
1,916✔
457
                        if (it[0] == '\n') {
1,852✔
458
                                it[0] = ',';
12✔
459
                        }
460
                }
461
                return n_read;
462
        } else if (closing_rem_ > 0) {
16✔
463
                io::Vsize target_size = end - start;
8✔
464
                auto copy_end = copy_n(
8✔
465
                        closing_.begin() + (closing_.size() - closing_rem_),
8✔
466
                        min(closing_rem_, target_size),
467
                        start);
468
                auto n_copied = copy_end - start;
469
                closing_rem_ -= n_copied;
8✔
470
                return static_cast<size_t>(copy_end - start);
471
        } else {
472
                return 0;
473
        }
474
};
475

476
static const string logs_uri_suffix = "/log";
477

478
error::Error DeploymentClient::PushLogs(
3✔
479
        const string &deployment_id,
480
        const string &log_file_path,
481
        api::Client &client,
482
        LogsAPIResponseHandler api_handler) {
483
        auto ex_size = GetLogFileDataSize(log_file_path);
3✔
484
        if (!ex_size) {
3✔
485
                // api_handler(ex_size.error()) ???
486
                return ex_size.error();
×
487
        }
488
        auto data_size = ex_size.value();
3✔
489

490
        auto file_reader = make_shared<io::FileReader>(log_file_path);
3✔
491
        auto logs_reader = make_shared<JsonLogMessagesReader>(file_reader, data_size);
3✔
492

493
        auto req = make_shared<api::APIRequest>();
3✔
494
        req->SetPath(http::JoinUrl(deployments_uri_prefix, deployment_id, logs_uri_suffix));
3✔
495
        req->SetMethod(http::Method::PUT);
3✔
496
        req->SetHeader("Content-Type", "application/json");
6✔
497
        req->SetHeader("Content-Length", to_string(JsonLogMessagesReader::TotalDataSize(data_size)));
6✔
498
        req->SetHeader("Accept", "application/json");
6✔
499
        req->SetBodyGenerator([logs_reader]() {
15✔
500
                logs_reader->Rewind();
6✔
501
                return logs_reader;
3✔
502
        });
503

504
        auto received_body = make_shared<vector<uint8_t>>();
3✔
505
        return client.AsyncCall(
12✔
506
                req,
507
                [this, received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
6✔
508
                        this->PushLogsHeaderHandler(received_body, api_handler, exp_resp);
9✔
509
                },
3✔
510
                [received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
9✔
511
                        if (!exp_resp) {
3✔
512
                                log::Error("Request to push logs data failed: " + exp_resp.error().message);
×
NEW
513
                                api_handler(LogsAPIResponse {nullopt, nullopt, exp_resp.error()});
×
514
                                return;
×
515
                        }
516

517
                        auto resp = exp_resp.value();
3✔
518
                        auto status = resp->GetStatusCode();
3✔
519

520
                        // StatusTooManyRequests must have been handled in PushLogsHeaderHandler already
521
                        assert(status != http::StatusTooManyRequests);
522

523
                        if (status == http::StatusNoContent) {
3✔
524
                                api_handler(LogsAPIResponse {status, nullopt, error::NoError});
2✔
525
                        } else {
526
                                auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
1✔
527
                                string err_str;
528
                                if (ex_err_msg) {
1✔
529
                                        err_str = ex_err_msg.value();
1✔
530
                                } else {
531
                                        err_str = resp->GetStatusMessage();
×
532
                                }
533
                                api_handler(LogsAPIResponse {
2✔
534
                                        status,
535
                                        nullopt,
536
                                        MakeError(
537
                                                BadResponseError,
538
                                                "Got unexpected response " + to_string(status)
1✔
539
                                                        + " from logs API: " + err_str)});
2✔
540
                        }
541
                });
3✔
542
}
543

544
void DeploymentClient::PushLogsHeaderHandler(
6✔
545
        shared_ptr<vector<uint8_t>> received_body,
546
        LogsAPIResponseHandler api_handler,
547
        http::ExpectedIncomingResponsePtr exp_resp) {
548
        if (!exp_resp) {
6✔
NEW
549
                log::Error("Request to push logs data failed: " + exp_resp.error().message);
×
NEW
550
                api_handler(LogsAPIResponse {nullopt, nullopt, exp_resp.error()});
×
551
                return;
3✔
552
        }
553

554
        auto body_writer = make_shared<io::ByteWriter>(received_body);
6✔
555
        auto resp = exp_resp.value();
6✔
556
        auto status = resp->GetStatusCode();
6✔
557
        if (status == http::StatusTooManyRequests) {
6✔
558
                LogsAPIResponse response = {
559
                        status, resp->GetHeaders(), MakeError(TooManyRequestsError, "Too many requests")};
3✔
560
                api_handler(response);
3✔
561
                return;
562
        }
3✔
563
        auto content_length = resp->GetHeader("Content-Length");
6✔
564
        if (!content_length) {
3✔
NEW
565
                log::Debug(
×
566
                        "Failed to get content length from the status API response headers: "
NEW
567
                        + content_length.error().String());
×
NEW
568
                body_writer->SetUnlimited(true);
×
569
        } else {
570
                auto ex_len = common::StringTo<size_t>(content_length.value());
3✔
571
                if (!ex_len) {
3✔
NEW
572
                        log::Error(
×
573
                                "Failed to convert the content length from the status API response headers to an integer: "
NEW
574
                                + ex_len.error().String());
×
NEW
575
                        body_writer->SetUnlimited(true);
×
576
                } else {
577
                        received_body->resize(ex_len.value());
3✔
578
                }
579
        }
580
        resp->SetBodyWriter(body_writer);
6✔
581
}
582

583
} // namespace deployments
584
} // namespace update
585
} // 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