• 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

84.14
/src/mender-update/daemon/states.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/states.hpp>
16

17
#include <client_shared/conf.hpp>
18
#include <common/events_io.hpp>
19
#include <common/log.hpp>
20
#include <common/path.hpp>
21

22
#include <mender-update/daemon/context.hpp>
23
#include <mender-update/inventory.hpp>
24

25
namespace mender {
26
namespace update {
27
namespace daemon {
28

29
namespace conf = mender::client_shared::conf;
30
namespace error = mender::common::error;
31
namespace events = mender::common::events;
32
namespace kv_db = mender::common::key_value_database;
33
namespace path = mender::common::path;
34
namespace log = mender::common::log;
35

36
namespace main_context = mender::update::context;
37
namespace inventory = mender::update::inventory;
38

39
class DefaultStateHandler {
40
public:
41
        void operator()(const error::Error &err) {
294✔
42
                if (err != error::NoError) {
294✔
43
                        log::Error(err.String());
23✔
44
                        poster.PostEvent(StateEvent::Failure);
23✔
45
                        return;
23✔
46
                }
47
                poster.PostEvent(StateEvent::Success);
271✔
48
        }
49

50
        sm::EventPoster<StateEvent> &poster;
51
};
52

53
static void DefaultAsyncErrorHandler(sm::EventPoster<StateEvent> &poster, const error::Error &err) {
412✔
54
        if (err != error::NoError) {
412✔
55
                log::Error(err.String());
×
56
                poster.PostEvent(StateEvent::Failure);
×
57
        }
58
}
412✔
59

60
void EmptyState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
152✔
61
        // Keep this state truly empty.
62
}
152✔
63

64
void InitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
59✔
65
        // I will never run - just a placeholder to start the state-machine at
66
        poster.PostEvent(StateEvent::Started); // Start the state machine
59✔
67
}
59✔
68

69
void StateScriptState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
1,064✔
70
        string state_name {script_executor::Name(this->state_, this->action_)};
1,064✔
71
        log::Debug("Executing the  " + state_name + " State Scripts...");
1,064✔
72
        auto err = this->script_.AsyncRunScripts(
2,128✔
73
                this->state_,
74
                this->action_,
75
                [state_name, &poster](error::Error err) {
8,945✔
76
                        if (err != error::NoError) {
1,064✔
77
                                log::Error(
21✔
78
                                        "Received error: (" + err.String() + ") when running the State Script scripts "
21✔
79
                                        + state_name);
42✔
80
                                poster.PostEvent(StateEvent::Failure);
21✔
81
                                return;
21✔
82
                        }
83
                        log::Debug("Successfully ran the " + state_name + " State Scripts...");
1,043✔
84
                        poster.PostEvent(StateEvent::Success);
1,043✔
85
                },
86
                this->on_error_);
2,128✔
87

88
        if (err != error::NoError) {
1,064✔
89
                log::Error(
×
90
                        "Failed to schedule the state script execution for: " + state_name
×
91
                        + " got error: " + err.String());
×
92
                poster.PostEvent(StateEvent::Failure);
×
93
                return;
94
        }
95
}
96

97

98
void SaveStateScriptState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
278✔
99
        return state_script_state_.OnEnter(ctx, poster);
278✔
100
}
101

102
void IdleState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
123✔
103
        log::Debug("Entering Idle state");
123✔
104
}
123✔
105

106
ScheduleNextPollState::ScheduleNextPollState(
192✔
107
        events::Timer &timer, const string &poll_action, const StateEvent event, int interval) :
192✔
108
        timer_ {timer},
192✔
109
        poll_action_ {poll_action},
192✔
110
        event_ {event},
192✔
111
        interval_ {interval} {
192✔
112
}
192✔
113

114
void ScheduleNextPollState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
122✔
115
        log::Debug("Scheduling the next " + poll_action_ + " in: " + to_string(interval_) + " seconds");
244✔
116
        timer_.AsyncWait(chrono::seconds(interval_), [this, &poster](error::Error err) {
122✔
117
                if (err != error::NoError) {
8✔
118
                        if (err.code != make_error_condition(errc::operation_canceled)) {
5✔
119
                                log::Error("Timer caused error: " + err.String());
×
120
                        }
121
                } else {
122
                        poster.PostEvent(event_);
3✔
123
                }
124
        });
8✔
125

126
        poster.PostEvent(StateEvent::Success);
122✔
127
}
122✔
128

129
SubmitInventoryState::SubmitInventoryState(int retry_interval_seconds, int retry_count) :
198✔
130
        backoff_ {chrono::seconds(retry_interval_seconds), retry_count} {
99✔
131
}
198✔
132

133
void SubmitInventoryState::HandlePollingError(
14✔
134
        Context &ctx, sm::EventPoster<StateEvent> &poster, inventory::APIResponse response) {
135
        // When using short polling intervals, we should adjust the backoff to ensure
136
        // that the intervals do not exceed the maximum retry polling interval, which
137
        // converts the backoff to a fixed interval.
138
        chrono::milliseconds max_interval =
139
                chrono::seconds(ctx.mender_context.GetConfig().retry_poll_interval_seconds);
14✔
140
        if (max_interval < backoff_.SmallestInterval()) {
14✔
141
                backoff_.SetSmallestInterval(max_interval);
×
142
                backoff_.SetMaxInterval(max_interval);
×
143
        }
144

145
        chrono::milliseconds interval;
146
        bool retry_after_defined {false};
147

148
        if (response.http_code.has_value() && response.http_code.value() == http::StatusTooManyRequests
14✔
149
                && response.http_headers.has_value()) {
24✔
150
                auto retry_after_header = response.http_headers.value().find("Retry-After");
10✔
151
                if (retry_after_header != response.http_headers.value().end()) {
10✔
152
                        auto exp_interval = http::GetRemainingTime(retry_after_header->second);
3✔
153
                        if (exp_interval) {
3✔
154
                                interval = exp_interval.value();
3✔
155
                                retry_after_defined = true;
156
                        } else {
NEW
157
                                log::Debug("Could not get the Retry-After value from HTTP response");
×
158
                        }
159
                }
160
        }
161

162
        if (!retry_after_defined) {
3✔
163
                auto exp_interval = backoff_.NextInterval();
11✔
164
                if (!exp_interval) {
11✔
NEW
165
                        log::Debug(
×
166
                                "Not retrying with backoff, retrying InventoryPollIntervalSeconds: "
NEW
167
                                + exp_interval.error().String());
×
168
                        return;
169
                }
170
                interval = exp_interval.value();
11✔
171
        }
172
        log::Info(
14✔
173
                "Retrying inventory polling in "
174
                + to_string(chrono::duration_cast<chrono::seconds>(interval).count()) + " seconds");
14✔
175

176
        ctx.inventory_timer.Cancel();
14✔
177
        ctx.inventory_timer.AsyncWait(interval, [&poster](error::Error err) {
28✔
178
                if (err != error::NoError) {
×
179
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
180
                                log::Error("Retry poll timer caused error: " + err.String());
×
181
                        }
182
                } else {
183
                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
184
                }
185
        });
×
186
}
187

188
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
59✔
189
        log::Debug("Submitting inventory");
59✔
190

191
        auto handler = [this, &ctx, &poster](inventory::APIResponse resp) {
59✔
192
                this->PushDataHandler(ctx, poster, resp);
59✔
193
        };
118✔
194

195
        auto err = ctx.inventory_client->PushData(
59✔
196
                ctx.mender_context.GetConfig().paths.GetInventoryScriptsDir(),
59✔
197
                ctx.event_loop,
198
                ctx.http_client,
199
                handler);
59✔
200

201
        if (err != error::NoError) {
59✔
202
                // This is the only case the handler won't be called for us by
203
                // PushData() (see inventory::PushInventoryData()).
NEW
204
                PushDataHandler(ctx, poster, inventory::APIResponse {nullopt, nullopt, err});
×
205
        }
206
}
59✔
207

208
PollForDeploymentState::PollForDeploymentState(int retry_interval_seconds, int retry_count) :
198✔
209
        backoff_ {chrono::seconds(retry_interval_seconds), retry_count} {
99✔
210
}
198✔
211

212
void SubmitInventoryState::PushDataHandler(
73✔
213
        Context &ctx, sm::EventPoster<StateEvent> &poster, inventory::APIResponse resp) {
214
        if (resp.error != error::NoError) {
73✔
215
                log::Error("Failed to submit inventory: " + resp.error.String());
14✔
216
                // Replace the inventory poll timer with:
217
                // - a backoff, or
218
                // - if HTTP 429 Too Many Requests with  Retry-After header is provided - appropriate time
219
                // based on it
220
                HandlePollingError(ctx, poster, resp);
14✔
221
                poster.PostEvent(StateEvent::Failure);
14✔
222
                return;
14✔
223
        }
224
        backoff_.Reset();
225
        ctx.inventory_client->has_submitted_inventory = true;
59✔
226
        poster.PostEvent(StateEvent::Success);
59✔
227
}
228

229
void PollForDeploymentState::HandlePollingError(
18✔
230
        Context &ctx,
231
        sm::EventPoster<StateEvent> &poster,
232
        deployments::CheckUpdatesAPIResponseError error) {
233
        // When using short polling intervals, we should adjust the backoff to ensure
234
        // that the intervals do not exceed the maximum retry polling interval, which
235
        // converts the backoff to a fixed interval.
236
        chrono::milliseconds max_interval =
237
                chrono::seconds(ctx.mender_context.GetConfig().retry_poll_interval_seconds);
18✔
238
        if (max_interval < backoff_.SmallestInterval()) {
18✔
239
                backoff_.SetSmallestInterval(max_interval);
1✔
240
                backoff_.SetMaxInterval(max_interval);
1✔
241
        }
242

243
        chrono::milliseconds interval;
244
        bool retry_after_defined {false};
245

246
        if (error.http_code.has_value() && error.http_code.value() == http::StatusTooManyRequests
14✔
247
                && error.http_headers.has_value()) {
28✔
248
                auto retry_after_header = error.http_headers.value().find("Retry-After");
10✔
249
                if (retry_after_header != error.http_headers.value().end()) {
10✔
250
                        auto exp_interval = http::GetRemainingTime(retry_after_header->second);
3✔
251
                        if (exp_interval) {
3✔
252
                                interval = exp_interval.value();
3✔
253
                                retry_after_defined = true;
254
                        } else {
255
                                log::Debug("Could not get the Retry-After value from HTTP response");
×
256
                        }
257
                } else {
258
                        log::Debug("Got status code TooManyRequests but no Retry-After HTTP header");
14✔
259
                }
260
        }
261

262
        if (!retry_after_defined) {
3✔
263
                auto exp_interval = backoff_.NextInterval();
15✔
264
                if (!exp_interval) {
15✔
265
                        log::Debug(
1✔
266
                                "Not retrying with backoff, retrying with UpdatePollIntervalSeconds: "
267
                                + exp_interval.error().String());
2✔
268
                        return;
269
                }
270
                interval = exp_interval.value();
14✔
271
        }
272
        log::Info(
17✔
273
                "Retrying deployment polling in "
274
                + to_string(chrono::duration_cast<chrono::seconds>(interval).count()) + " seconds");
17✔
275

276
        ctx.deployment_timer.Cancel();
17✔
277
        ctx.deployment_timer.AsyncWait(interval, [&poster](error::Error err) {
34✔
278
                if (err != error::NoError) {
3✔
279
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
280
                                log::Error("Retry poll timer caused error: " + err.String());
×
281
                        }
282
                } else {
283
                        poster.PostEvent(StateEvent::DeploymentPollingTriggered);
3✔
284
                }
285
        });
3✔
286
}
287

288
void PollForDeploymentState::CheckNewDeploymentsHandler(
72✔
289
        Context &ctx,
290
        sm::EventPoster<StateEvent> &poster,
291
        deployments::CheckUpdatesAPIResponse response) {
292
        if (!response) {
72✔
293
                log::Error("Error while polling for deployment: " + response.error().error.String());
28✔
294
                // Replace the update poll timer with:
295
                // - a backoff, or
296
                // - if HTTP 429 Too Many Requests with  Retry-After header is provided - appropriate time
297
                // based on it
298
                HandlePollingError(ctx, poster, response.error());
14✔
299
                poster.PostEvent(StateEvent::Failure);
14✔
300
                return;
15✔
301
        } else if (!response.value()) {
58✔
302
                log::Info("No update available");
1✔
303
                poster.PostEvent(StateEvent::NothingToDo);
1✔
304
                if (not ctx.inventory_client->has_submitted_inventory) {
1✔
305
                        // If we have not submitted inventory successfully at least
306
                        // once, schedule this after receiving a successful response
307
                        // with no update. This enables inventory to be submitted
308
                        // immediately after the device has been accepted. If there
309
                        // is an update available, an inventory update will be
310
                        // scheduled at the end of it unconditionally.
311
                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
312
                }
313

314
                backoff_.Reset();
315
                return;
1✔
316
        }
317
        backoff_.Reset();
318

319
        auto exp_data = ApiResponseJsonToStateData(response.value().value());
57✔
320
        if (!exp_data) {
57✔
321
                log::Error("Error in API response: " + exp_data.error().String());
×
322
                poster.PostEvent(StateEvent::Failure);
×
323
                return;
324
        }
325

326
        // Make a new set of update data.
327
        ctx.deployment.state_data.reset(new StateData(std::move(exp_data.value())));
57✔
328

329
        ctx.BeginDeploymentLogging();
57✔
330

331
        // This is a duplicate message to one logged when mender-update
332
        // starts, but this one goes into the deployment log.
333
        log::Info("Running mender-update " + conf::kMenderVersion);
114✔
334
        log::Info("Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
57✔
335

336
        poster.PostEvent(StateEvent::DeploymentStarted);
57✔
337
        poster.PostEvent(StateEvent::Success);
57✔
338
}
339

340
void PollForDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
62✔
341
        log::Debug("Polling for update");
124✔
342

343
        auto err = ctx.deployment_client->CheckNewDeployments(
124✔
344
                ctx.mender_context,
345
                ctx.http_client,
346
                [this, &ctx, &poster](deployments::CheckUpdatesAPIResponse response) {
62✔
347
                        this->CheckNewDeploymentsHandler(ctx, poster, response);
58✔
348
                });
120✔
349

350
        if (err != error::NoError) {
62✔
351
                log::Error("Error when trying to poll for deployment: " + err.String());
4✔
352
                // If we're here, no handler will be called, so we need to manually schedule the next
353
                // deployment poll.
354
                // Posting Failure correctly exits the PollForDeploymentState, but does not schedule the
355
                // next poll. Thus, we need to also call HandlePollingError that adds a timer that will
356
                // cause DeploymentPollingTriggered to be posted to the state machine after a defined time.
357
                HandlePollingError(
4✔
358
                        ctx,
359
                        poster,
360
                        mender::update::deployments::CheckUpdatesAPIResponseError {nullopt, nullopt, err});
8✔
361
                poster.PostEvent(StateEvent::Failure);
4✔
362
        }
363
}
62✔
364

365
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
535✔
366
        assert(ctx.deployment.state_data);
367

368
        ctx.deployment.state_data->state = DatabaseStateString();
535✔
369

370
        log::Trace("Storing deployment state in the DB (database-string): " + DatabaseStateString());
1,070✔
371

372
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
535✔
373
        if (err != error::NoError) {
535✔
374
                log::Error(err.String());
10✔
375
                if (err.code
10✔
376
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
20✔
377
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
378
                        return;
379
                } else if (!IsFailureState()) {
9✔
380
                        // Non-failure states should be interrupted, but failure states should be
381
                        // allowed to do their work, even if a database error was detected.
382
                        poster.PostEvent(StateEvent::Failure);
2✔
383
                        return;
384
                }
385
        }
386

387
        OnEnterSaveState(ctx, poster);
532✔
388
}
389

390
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
391
        log::Debug("Entering Download state");
54✔
392

393
        auto req = make_shared<http::OutgoingRequest>();
54✔
394
        req->SetMethod(http::Method::GET);
54✔
395
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
54✔
396
        if (err != error::NoError) {
54✔
397
                log::Error(err.String());
×
398
                poster.PostEvent(StateEvent::Failure);
×
399
                return;
400
        }
401

402
        err = ctx.download_client->AsyncCall(
108✔
403
                req,
404
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
54✔
405
                        if (!exp_resp) {
54✔
406
                                log::Error("Unexpected error during download: " + exp_resp.error().String());
×
407
                                poster.PostEvent(StateEvent::Failure);
×
408
                                return;
1✔
409
                        }
410

411
                        auto &resp = exp_resp.value();
54✔
412
                        if (resp->GetStatusCode() != http::StatusOK) {
54✔
413
                                log::Error(
1✔
414
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
1✔
415
                                poster.PostEvent(StateEvent::Failure);
1✔
416
                                return;
1✔
417
                        }
418

419
                        auto http_reader = resp->MakeBodyAsyncReader();
53✔
420
                        if (!http_reader) {
53✔
421
                                log::Error(http_reader.error().String());
×
422
                                poster.PostEvent(StateEvent::Failure);
×
423
                                return;
424
                        }
425
                        ctx.deployment.artifact_reader =
53✔
426
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
53✔
427
                        ParseArtifact(ctx, poster);
53✔
428
                },
429
                [](http::ExpectedIncomingResponsePtr exp_resp) {
54✔
430
                        if (!exp_resp) {
54✔
431
                                log::Error(exp_resp.error().String());
6✔
432
                                // Cannot handle error here, because this handler is called at the
433
                                // end of the download, when we have already left this state. So
434
                                // rely on this error being propagated through the BodyAsyncReader
435
                                // above instead.
436
                                return;
6✔
437
                        }
438
                });
54✔
439

440
        if (err != error::NoError) {
54✔
441
                log::Error(err.String());
×
442
                poster.PostEvent(StateEvent::Failure);
×
443
                return;
444
        }
445
}
446

447
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
53✔
448
        string art_scripts_path = ctx.mender_context.GetConfig().paths.GetArtScriptsPath();
53✔
449

450
        // Clear the artifact scripts directory so we don't risk old scripts lingering.
451
        auto err = path::DeleteRecursively(art_scripts_path);
53✔
452
        if (err != error::NoError) {
53✔
453
                log::Error("When preparing to parse artifact: " + err.String());
×
454
                poster.PostEvent(StateEvent::Failure);
×
455
                return;
456
        }
457

458
        artifact::config::ParserConfig config {
53✔
459
                .artifact_scripts_filesystem_path = art_scripts_path,
460
                .artifact_scripts_version = 3,
461
                .artifact_verify_keys = ctx.mender_context.GetConfig().artifact_verify_keys,
53✔
462
        };
53✔
463
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
53✔
464
        if (!exp_parser) {
53✔
465
                log::Error(exp_parser.error().String());
×
466
                poster.PostEvent(StateEvent::Failure);
×
467
                return;
468
        }
469
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
53✔
470

471
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
53✔
472
        if (!exp_header) {
53✔
473
                log::Error(exp_header.error().String());
×
474
                poster.PostEvent(StateEvent::Failure);
×
475
                return;
476
        }
477
        auto &header = exp_header.value();
53✔
478

479
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
53✔
480
        if (!exp_matches) {
53✔
481
                log::Error(exp_matches.error().String());
2✔
482
                poster.PostEvent(StateEvent::Failure);
2✔
483
                return;
484
        } else if (!exp_matches.value()) {
51✔
485
                // reasons already logged
486
                poster.PostEvent(StateEvent::Failure);
1✔
487
                return;
488
        }
489

490
        log::Info("Installing artifact...");
100✔
491

492
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
50✔
493

494
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
50✔
495

496
        assert(ctx.deployment.state_data->update_info.artifact.payload_types.size() == 1);
497

498
        // Initial state data save, now that we have enough information from the artifact.
499
        err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
50✔
500
        if (err != error::NoError) {
50✔
501
                log::Error(err.String());
×
502
                if (err.code
×
503
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
504
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
505
                        return;
506
                } else {
507
                        poster.PostEvent(StateEvent::Failure);
×
508
                        return;
509
                }
510
        }
511

512
        if (header.header.payload_type == "") {
50✔
513
                // Empty-payload-artifact, aka "bootstrap artifact".
514
                poster.PostEvent(StateEvent::NothingToDo);
1✔
515
                return;
516
        }
517

518
        auto exp_update_module =
519
                update_module::UpdateModule::Create(ctx.mender_context, header.header.payload_type);
49✔
520
        if (!exp_update_module.has_value()) {
49✔
521
                log::Error(
×
522
                        "Error creating an Update Module when parsing artifact: "
523
                        + exp_update_module.error().String());
×
524
                poster.PostEvent(StateEvent::Failure);
×
525
                return;
526
        }
527
        ctx.deployment.update_module = std::move(exp_update_module.value());
49✔
528

529
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
49✔
530
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
49✔
531
        if (err != error::NoError) {
49✔
532
                log::Error(err.String());
×
533
                poster.PostEvent(StateEvent::Failure);
×
534
                return;
535
        }
536

537
        err = ctx.deployment.update_module->AsyncProvidePayloadFileSizes(
49✔
538
                ctx.event_loop, [&ctx, &poster](expected::ExpectedBool download_with_sizes) {
49✔
539
                        if (!download_with_sizes.has_value()) {
49✔
540
                                log::Error(download_with_sizes.error().String());
×
541
                                poster.PostEvent(StateEvent::Failure);
×
542
                                return;
×
543
                        }
544
                        ctx.deployment.download_with_sizes = download_with_sizes.value();
49✔
545
                        DoDownload(ctx, poster);
49✔
546
                });
49✔
547

548
        if (err != error::NoError) {
49✔
549
                log::Error(err.String());
×
550
                poster.PostEvent(StateEvent::Failure);
×
551
                return;
552
        }
553
}
53✔
554

555
void UpdateDownloadState::DoDownload(Context &ctx, sm::EventPoster<StateEvent> &poster) {
49✔
556
        auto exp_payload = ctx.deployment.artifact_parser->Next();
49✔
557
        if (!exp_payload) {
49✔
558
                log::Error(exp_payload.error().String());
×
559
                poster.PostEvent(StateEvent::Failure);
×
560
                return;
561
        }
562
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
49✔
563

564
        auto handler = [&poster, &ctx](error::Error err) {
49✔
565
                if (err != error::NoError) {
49✔
566
                        log::Error(err.String());
2✔
567
                        poster.PostEvent(StateEvent::Failure);
2✔
568
                        return;
2✔
569
                }
570

571
                auto exp_payload = ctx.deployment.artifact_parser->Next();
47✔
572
                if (exp_payload) {
47✔
573
                        log::Error("Multiple payloads are not yet supported in daemon mode.");
×
574
                        poster.PostEvent(StateEvent::Failure);
×
575
                        return;
576
                } else if (
47✔
577
                        exp_payload.error().code
578
                        != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
94✔
579
                        log::Error(exp_payload.error().String());
×
580
                        poster.PostEvent(StateEvent::Failure);
×
581
                        return;
582
                }
583

584
                poster.PostEvent(StateEvent::Success);
47✔
585
        };
586

587
        if (ctx.deployment.download_with_sizes) {
49✔
588
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
2✔
589
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
590
        } else {
591
                ctx.deployment.update_module->AsyncDownload(
96✔
592
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
593
        }
594
}
595

596
void UpdateDownloadCancelState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
597
        log::Debug("Entering DownloadCancel state");
12✔
598
        ctx.download_client->Cancel();
6✔
599
        poster.PostEvent(StateEvent::Success);
6✔
600
}
6✔
601

602
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
288✔
603
        status_(status),
288✔
604
        mode_(FailureMode::Ignore) {
288✔
605
}
288✔
606

607
SendStatusUpdateState::SendStatusUpdateState(
390✔
608
        optional<deployments::DeploymentStatus> status,
609
        events::EventLoop &event_loop,
610
        int retry_interval_seconds,
611
        int retry_count) :
195✔
612
        status_(status),
195✔
613
        mode_(FailureMode::RetryThenFail),
195✔
614
        retry_(Retry {
195✔
615
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
616
                event_loop}) {
195✔
617
}
390✔
618

619
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
182✔
620
        if (retry_) {
182✔
621
                retry_->backoff.SetSmallestInterval(interval);
182✔
622
        }
623
}
182✔
624

625
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
246✔
626
        // Reset this every time we enter the state, which means a new round of retries.
627
        if (retry_) {
246✔
628
                retry_->backoff.Reset();
629
        }
630

631
        DoStatusUpdate(ctx, poster);
246✔
632
}
246✔
633

634
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
265✔
635
        assert(ctx.deployment_client);
636
        assert(ctx.deployment.state_data);
637

638
        log::Info("Sending status update to server");
265✔
639

640
        auto result_handler = [this, &ctx, &poster](deployments::APIResponseError error) {
265✔
641
                this->DoStatusUpdateHandler(ctx, poster, error);
265✔
642
        };
530✔
643

644
        deployments::DeploymentStatus status;
645
        if (status_) {
265✔
646
                status = status_.value();
172✔
647
        } else {
648
                // If nothing is specified, grab success/failure status from the deployment status.
649
                if (ctx.deployment.failed) {
93✔
650
                        status = deployments::DeploymentStatus::Failure;
651
                } else {
652
                        status = deployments::DeploymentStatus::Success;
653
                }
654
        }
655

656
        // Push status.
657
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
530✔
658
        auto err = ctx.deployment_client->PushStatus(
265✔
659
                ctx.deployment.state_data->update_info.id,
265✔
660
                status,
661
                "",
662
                ctx.http_client,
663
                [result_handler, &ctx](deployments::StatusAPIResponse error) {
265✔
664
                        // If there is an error, we don't submit logs now, but call the handler,
665
                        // which may schedule a retry later. If there is no error, and the
666
                        // deployment as a whole was successful, then also call the handler here,
667
                        // since we don't need to submit logs at all then.
668
                        if (error.error != error::NoError || !ctx.deployment.failed) {
265✔
669
                                result_handler(error);
190✔
670
                                return;
190✔
671
                        }
672

673
                        // Push logs.
674
                        auto err = ctx.deployment_client->PushLogs(
150✔
675
                                ctx.deployment.state_data->update_info.id,
75✔
676
                                ctx.deployment.logger->LogFilePath(),
150✔
677
                                ctx.http_client,
75✔
678
                                result_handler);
75✔
679

680
                        if (err != error::NoError) {
75✔
NEW
681
                                result_handler(deployments::APIResponseError {nullopt, nullopt, err});
×
682
                        }
683
                });
265✔
684

685
        if (err != error::NoError) {
265✔
NEW
686
                result_handler(deployments::APIResponseError {nullopt, nullopt, err});
×
687
        }
688

689
        // No action, wait for reply from status endpoint.
690
}
265✔
691

692
void SendStatusUpdateState::DoStatusUpdateHandler(
279✔
693
        Context &ctx, sm::EventPoster<StateEvent> &poster, deployments::APIResponseError error) {
694
        if (error.error != error::NoError) {
279✔
695
                log::Error("Could not send deployment status: " + error.error.String());
39✔
696

697
                if (error.error.code
78✔
698
                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
78✔
699
                        // If the deployment was aborted upstream it is an immediate
700
                        // failure, even if retry is enabled.
701
                        poster.PostEvent(StateEvent::DeploymentAborted);
3✔
702
                        return;
3✔
703
                }
704

705
                switch (mode_) {
36✔
706
                case FailureMode::Ignore:
707
                        break;
708
                case FailureMode::RetryThenFail:
34✔
709
                        chrono::milliseconds interval;
710
                        bool retry_after_defined {false};
711

712
                        if (error.http_code.has_value()
713
                                && error.http_code.value() == http::StatusTooManyRequests
14✔
714
                                && error.http_headers.has_value()) {
44✔
715
                                auto retry_after_header = error.http_headers.value().find("Retry-After");
10✔
716
                                if (retry_after_header != error.http_headers.value().end()) {
10✔
717
                                        auto exp_interval = http::GetRemainingTime(retry_after_header->second);
3✔
718
                                        if (exp_interval) {
3✔
719
                                                interval = exp_interval.value();
3✔
720
                                                retry_after_defined = true;
721
                                        } else {
NEW
722
                                                log::Debug("Could not get the Retry-After value from HTTP response");
×
723
                                        }
724
                                }
725
                        }
726

727
                        if (!retry_after_defined) {
3✔
728
                                auto exp_interval = retry_->backoff.NextInterval();
31✔
729
                                if (!exp_interval) {
31✔
730
                                        log::Error(
1✔
731
                                                "Giving up on sending status updates to server: "
732
                                                + exp_interval.error().String());
1✔
733
                                        poster.PostEvent(StateEvent::Failure);
1✔
734
                                        return;
735
                                }
736
                                interval = exp_interval.value();
30✔
737
                        }
738

739
                        log::Info(
33✔
740
                                "Retrying status update after "
741
                                + to_string(chrono::duration_cast<chrono::seconds>(interval).count()) + " seconds");
33✔
742
                        retry_->wait_timer.AsyncWait(interval, [this, &ctx, &poster](error::Error err) {
33✔
743
                                // Error here is quite unexpected (from a timer), so treat
744
                                // this as an immediate error, despite Retry flag.
745
                                if (err != error::NoError) {
19✔
NEW
746
                                        log::Error(
×
NEW
747
                                                "Unexpected error in SendStatusUpdateState wait timer: " + err.String());
×
NEW
748
                                        poster.PostEvent(StateEvent::Failure);
×
NEW
749
                                        return;
×
750
                                }
751

752
                                // Try again. Since both status and logs are sent
753
                                // from here, there's a chance this might resubmit
754
                                // the status, but there's no harm in it, and it
755
                                // won't happen often.
756
                                DoStatusUpdate(ctx, poster);
19✔
757
                        });
758
                        return;
33✔
759
                }
760
        }
761

762
        poster.PostEvent(StateEvent::Success);
242✔
763
}
764

765
void UpdateInstallState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
42✔
766
        log::Debug("Entering ArtifactInstall state");
84✔
767

768
        DefaultAsyncErrorHandler(
42✔
769
                poster,
770
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
771
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
772
}
42✔
773

774
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
775
        DefaultAsyncErrorHandler(
73✔
776
                poster,
777
                ctx.deployment.update_module->AsyncNeedsReboot(
73✔
778
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
73✔
779
                                if (!reboot_action.has_value()) {
73✔
780
                                        log::Error(reboot_action.error().String());
2✔
781
                                        poster.PostEvent(StateEvent::Failure);
2✔
782
                                        return;
2✔
783
                                }
784

785
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
71✔
786
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
71✔
787
                                        NeedsRebootToDbString(*reboot_action);
71✔
788
                                switch (*reboot_action) {
71✔
789
                                case update_module::RebootAction::No:
8✔
790
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
791
                                        break;
8✔
792
                                case update_module::RebootAction::Yes:
63✔
793
                                case update_module::RebootAction::Automatic:
794
                                        poster.PostEvent(StateEvent::Success);
63✔
795
                                        break;
63✔
796
                                }
797
                        }));
798
}
73✔
799

800
void UpdateRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
26✔
801
        log::Debug("Entering ArtifactReboot state");
52✔
802

803
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
804
        auto exp_reboot_mode =
805
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
26✔
806
        // Should always be true because we check it at load time.
807
        assert(exp_reboot_mode);
808

809
        switch (exp_reboot_mode.value()) {
26✔
810
        case update_module::RebootAction::No:
×
811
                // Should not happen because then we don't enter this state.
812
                assert(false);
813
                poster.PostEvent(StateEvent::Failure);
×
814
                break;
815
        case update_module::RebootAction::Yes:
26✔
816
                DefaultAsyncErrorHandler(
26✔
817
                        poster,
818
                        ctx.deployment.update_module->AsyncArtifactReboot(
26✔
819
                                ctx.event_loop, DefaultStateHandler {poster}));
26✔
820
                break;
26✔
821
        case update_module::RebootAction::Automatic:
×
822
                DefaultAsyncErrorHandler(
×
823
                        poster,
824
                        ctx.deployment.update_module->AsyncSystemReboot(
×
825
                                ctx.event_loop, DefaultStateHandler {poster}));
×
826
                break;
×
827
        }
828
}
26✔
829

830
void UpdateVerifyRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
831
        log::Debug("Entering ArtifactVerifyReboot state");
58✔
832

833
        ctx.deployment.update_module->EnsureRootfsImageFileTree(
29✔
834
                ctx.deployment.update_module->GetUpdateModuleWorkDir());
29✔
835

836
        DefaultAsyncErrorHandler(
29✔
837
                poster,
838
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
29✔
839
                        ctx.event_loop, DefaultStateHandler {poster}));
29✔
840
}
29✔
841

842
void UpdateBeforeCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
22✔
843
        // It's possible that the update we have done has changed our credentials. Therefore it's
844
        // important that we try to log in from scratch and do not use the token we already have.
845
        ctx.http_client.ExpireToken();
22✔
846

847
        poster.PostEvent(StateEvent::Success);
22✔
848
}
22✔
849

850
void UpdateCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
851
        log::Debug("Entering ArtifactCommit state");
19✔
852

853
        // Explicitly check if state scripts version is supported
854
        auto err = script_executor::CheckScriptsCompatibility(
855
                ctx.mender_context.GetConfig().paths.GetRootfsScriptsPath());
19✔
856
        if (err != error::NoError) {
19✔
857
                log::Error("Failed script compatibility check: " + err.String());
×
858
                poster.PostEvent(StateEvent::Failure);
×
859
                return;
860
        }
861

862
        DefaultAsyncErrorHandler(
19✔
863
                poster,
864
                ctx.deployment.update_module->AsyncArtifactCommit(
19✔
865
                        ctx.event_loop, DefaultStateHandler {poster}));
38✔
866
}
867

868
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
869
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
870
        assert(ctx.deployment.state_data);
871
        auto &state_data = *ctx.deployment.state_data;
872
        if (state_data.update_info.has_db_schema_update) {
19✔
873
                state_data.update_info.has_db_schema_update = false;
×
874
                auto err = ctx.SaveDeploymentStateData(state_data);
×
875
                if (err != error::NoError) {
×
876
                        log::Error("Not able to commit schema update: " + err.String());
×
877
                        poster.PostEvent(StateEvent::Failure);
×
878
                        return;
879
                }
880
        }
881

882
        poster.PostEvent(StateEvent::Success);
19✔
883
}
884

885
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
45✔
886
        DefaultAsyncErrorHandler(
45✔
887
                poster,
888
                ctx.deployment.update_module->AsyncSupportsRollback(
45✔
889
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
45✔
890
                                if (!rollback_supported.has_value()) {
45✔
891
                                        log::Error(rollback_supported.error().String());
1✔
892
                                        poster.PostEvent(StateEvent::Failure);
1✔
893
                                        return;
1✔
894
                                }
895

896
                                ctx.deployment.state_data->update_info.supports_rollback =
44✔
897
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
898
                                if (*rollback_supported) {
44✔
899
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
900
                                        poster.PostEvent(StateEvent::Success);
38✔
901
                                } else {
902
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
903
                                }
904
                        }));
905
}
45✔
906

907
void UpdateRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
41✔
908
        log::Debug("Entering ArtifactRollback state");
82✔
909

910
        DefaultAsyncErrorHandler(
41✔
911
                poster,
912
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
913
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
914
}
41✔
915

916
void UpdateRollbackRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
917
        log::Debug("Entering ArtifactRollbackReboot state");
114✔
918

919
        auto exp_reboot_mode =
920
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
57✔
921
        // Should always be true because we check it at load time.
922
        assert(exp_reboot_mode);
923

924
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
925
        // succeeds.
926
        auto handler = [&poster](error::Error err) {
57✔
927
                if (err != error::NoError) {
57✔
928
                        log::Error(err.String());
2✔
929
                }
930
                poster.PostEvent(StateEvent::Success);
57✔
931
        };
57✔
932

933
        error::Error err;
57✔
934
        switch (exp_reboot_mode.value()) {
57✔
935
        case update_module::RebootAction::No:
936
                // Should not happen because then we don't enter this state.
937
                assert(false);
938

939
                err = error::MakeError(
×
940
                        error::ProgrammingError, "Entered UpdateRollbackRebootState with RebootAction = No");
×
941
                break;
×
942

943
        case update_module::RebootAction::Yes:
57✔
944
                err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(ctx.event_loop, handler);
57✔
945
                break;
57✔
946

947
        case update_module::RebootAction::Automatic:
×
948
                err = ctx.deployment.update_module->AsyncSystemReboot(ctx.event_loop, handler);
×
949
                break;
×
950
        }
951

952
        if (err != error::NoError) {
57✔
953
                log::Error(err.String());
×
954
                poster.PostEvent(StateEvent::Success);
×
955
        }
956
}
57✔
957

958
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
959
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
960
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
961

962
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
963
        // state loop detection will eventually kick in.
964
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
120✔
965
                ctx.event_loop, [&poster](error::Error err) {
60✔
966
                        if (err != error::NoError) {
60✔
967
                                log::Error(err.String());
22✔
968
                                poster.PostEvent(StateEvent::Retry);
22✔
969
                                return;
22✔
970
                        }
971
                        poster.PostEvent(StateEvent::Success);
38✔
972
                });
60✔
973
        if (err != error::NoError) {
60✔
974
                log::Error(err.String());
×
975
                poster.PostEvent(StateEvent::Retry);
×
976
        }
977
}
60✔
978

979
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
980
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
981
        poster.PostEvent(StateEvent::Success);
50✔
982
}
50✔
983

984
void UpdateFailureState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
55✔
985
        log::Debug("Entering ArtifactFailure state");
110✔
986

987
        DefaultAsyncErrorHandler(
55✔
988
                poster,
989
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
990
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
991
}
55✔
992

993
static string AddInconsistentSuffix(const string &str) {
21✔
994
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
995
        // `string::ends_with` is C++20... grumble
996
        string ret {str};
21✔
997
        if (!common::EndsWith(ret, suffix)) {
21✔
998
                ret.append(suffix);
21✔
999
        }
1000
        return ret;
21✔
1001
}
1002

1003
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
1004
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
1005
                // If the update failed, but we rolled back successfully, then we don't need to do
1006
                // anything, just keep the old data.
1007
                poster.PostEvent(StateEvent::Success);
38✔
1008
                return;
38✔
1009
        }
1010

1011
        assert(ctx.deployment.state_data);
1012
        // This state should never happen: rollback failed, but update not failed??
1013
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
1014

1015
        // We expect Cleanup to be the next state after this.
1016
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
37✔
1017

1018
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
1019

1020
        string artifact_name;
1021
        if (ctx.deployment.rollback_failed) {
37✔
1022
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
38✔
1023
        } else {
1024
                artifact_name = artifact.artifact_name;
18✔
1025
        }
1026

1027
        bool deploy_failed = ctx.deployment.failed;
37✔
1028

1029
        // Only the artifact_name and group should be committed in the case of a
1030
        // failing update in order to make this consistent with the old client
1031
        // behaviour.
1032
        auto err = ctx.mender_context.CommitArtifactData(
74✔
1033
                artifact_name,
1034
                artifact.artifact_group,
37✔
1035
                deploy_failed ? nullopt : optional<context::ProvidesData>(artifact.type_info_provides),
74✔
1036
                /* Special case: Keep existing provides */
1037
                deploy_failed ? context::ClearsProvidesData {}
74✔
1038
                                          : optional<context::ClearsProvidesData>(artifact.clears_artifact_provides),
18✔
1039
                [&ctx](kv_db::Transaction &txn) {
37✔
1040
                        // Save the Cleanup state together with the artifact data, atomically.
1041
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
37✔
1042
                });
37✔
1043
        if (err != error::NoError) {
37✔
1044
                log::Error("Error saving artifact data: " + err.String());
×
1045
                if (err.code
×
1046
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
1047
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
1048
                        return;
1049
                }
1050
                poster.PostEvent(StateEvent::Failure);
×
1051
                return;
1052
        }
1053

1054
        poster.PostEvent(StateEvent::Success);
37✔
1055
}
1056

1057
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
1058
        log::Debug("Entering ArtifactCleanup state");
182✔
1059

1060
        // It's possible for there not to be an initialized update_module structure, if the
1061
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
1062
        // no-op.
1063
        if (!ctx.deployment.update_module) {
91✔
1064
                poster.PostEvent(StateEvent::Success);
9✔
1065
                return;
9✔
1066
        }
1067

1068
        DefaultAsyncErrorHandler(
82✔
1069
                poster,
1070
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
164✔
1071
}
1072

1073
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
1074
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
93✔
1075
                // Remove state data, since we're done now.
1076
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
91✔
1077
                if (err != error::NoError) {
91✔
1078
                        return err;
×
1079
                }
1080
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
91✔
1081
        });
93✔
1082
        if (err != error::NoError) {
93✔
1083
                log::Error("Error removing artifact data: " + err.String());
2✔
1084
                poster.PostEvent(StateEvent::Failure);
2✔
1085
                return;
1086
        }
1087

1088
        poster.PostEvent(StateEvent::Success);
91✔
1089
}
1090

1091
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
1092
        assert(ctx.deployment.state_data);
1093
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
1094

1095
        // Mark update as inconsistent.
1096
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
1097

1098
        auto err = ctx.mender_context.CommitArtifactData(
6✔
1099
                artifact_name,
1100
                artifact.artifact_group,
2✔
1101
                artifact.type_info_provides,
2✔
1102
                artifact.clears_artifact_provides,
2✔
1103
                [](kv_db::Transaction &txn) { return error::NoError; });
4✔
1104
        if (err != error::NoError) {
2✔
1105
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
1106
                poster.PostEvent(StateEvent::Failure);
×
1107
                return;
1108
        }
1109

1110
        poster.PostEvent(StateEvent::Success);
2✔
1111
}
1112

1113
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
1114
        log::Info(
93✔
1115
                "Deployment with ID " + ctx.deployment.state_data->update_info.id
93✔
1116
                + " finished with status: " + string(ctx.deployment.failed ? "Failure" : "Success"));
297✔
1117

1118
        ctx.FinishDeploymentLogging();
93✔
1119

1120
        ctx.deployment = {};
93✔
1121
        poster.PostEvent(
93✔
1122
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
1123
        poster.PostEvent(StateEvent::DeploymentEnded);
93✔
1124
        poster.PostEvent(StateEvent::Success);
93✔
1125
}
93✔
1126

1127
ExitState::ExitState(events::EventLoop &event_loop) :
96✔
1128
        event_loop_(event_loop) {
96✔
1129
}
96✔
1130

1131
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
1132
#ifndef NDEBUG
1133
        if (--iterations_left_ <= 0) {
1134
                event_loop_.Stop();
1135
        } else {
1136
                poster.PostEvent(StateEvent::Success);
1137
        }
1138
#else
1139
        event_loop_.Stop();
93✔
1140
#endif
1141
}
93✔
1142

1143
namespace deployment_tracking {
1144

1145
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
63✔
1146
        ctx.deployment.failed = false;
63✔
1147
        ctx.deployment.rollback_failed = false;
63✔
1148
}
63✔
1149

1150
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
60✔
1151
        ctx.deployment.failed = true;
60✔
1152
        ctx.deployment.rollback_failed = true;
60✔
1153
}
60✔
1154

1155
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
1156
        ctx.deployment.failed = true;
52✔
1157
        ctx.deployment.rollback_failed = false;
52✔
1158
}
52✔
1159

1160
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
1161
        ctx.deployment.failed = true;
12✔
1162
        ctx.deployment.rollback_failed = true;
12✔
1163
}
12✔
1164

1165
} // namespace deployment_tracking
1166

1167
} // namespace daemon
1168
} // namespace update
1169
} // 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