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

mendersoftware / mender / 1635586027

22 Jan 2025 08:44AM UTC coverage: 76.021% (+0.08%) from 75.944%
1635586027

push

gitlab-ci

danielskinstad
fix: retry polling on all errors

When implementing the backoff for errors when polling for deployment or
submitting inventory, we added an exception for unauthorized errors.
After looking at how it was done in Mender Client 3, we discovered
that such an exception was not present there, and that the backoff
should be triggered for all types of errors.

Changelog: All errors on attempts to communicate with the server
are retried with an exponential backoff. This aligns the behavior
of the state machine with Mender Client 3.

Ticket: MEN-7938

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>

0 of 2 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

7390 of 9721 relevant lines covered (76.02%)

11126.56 hits per line

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

77.39
/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) {
295✔
42
                if (err != error::NoError) {
295✔
43
                        log::Error(err.String());
23✔
44
                        poster.PostEvent(StateEvent::Failure);
23✔
45
                        return;
23✔
46
                }
47
                poster.PostEvent(StateEvent::Success);
272✔
48
        }
49

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

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

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

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

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

88
        if (err != error::NoError) {
1,036✔
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) {
116✔
103
        log::Debug("Entering Idle state");
232✔
104
}
116✔
105

106
SubmitInventoryState::SubmitInventoryState(
94✔
107
        events::EventLoop &event_loop, int retry_interval_seconds, int retry_count) :
108
        retry_ {
109
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
110
                event_loop} {
188✔
111
}
94✔
112

113
void SubmitInventoryState::HandlePollingError(Context &ctx, sm::EventPoster<StateEvent> &poster) {
×
114
        // When using short polling inteervals, we should adjust the backoff to ensure
115
        // that the intervals do not exceed the maximum retry polling interval, which
116
        // converts the backoff to a fixed interval.
117
        chrono::milliseconds max_interval =
118
                chrono::seconds(ctx.mender_context.GetConfig().retry_poll_interval_seconds);
×
119
        if (max_interval < retry_.backoff.SmallestInterval()) {
×
120
                retry_.backoff.SetSmallestInterval(max_interval);
×
121
                retry_.backoff.SetMaxInterval(max_interval);
×
122
        }
123
        auto exp_interval = retry_.backoff.NextInterval();
×
124
        if (!exp_interval) {
×
125
                log::Debug(
×
126
                        "Not retrying with backoff, retrying InventoryPollIntervalSeconds: "
127
                        + exp_interval.error().String());
×
128
                return;
129
        }
130
        log::Info(
×
131
                "Retrying inventory polling in "
132
                + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count()) + " seconds");
×
133

134
        retry_.wait_timer.Cancel();
×
135
        retry_.wait_timer.AsyncWait(*exp_interval, [&poster](error::Error err) {
×
136
                if (err != error::NoError) {
×
137
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
138
                                log::Error("Retry poll timer caused error: " + err.String());
×
139
                        }
140
                } else {
141
                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
142
                }
143
        });
×
144
}
145

146
void SubmitInventoryState::DoSubmitInventory(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
147
        log::Debug("Submitting inventory");
114✔
148

149
        auto handler = [this, &ctx, &poster](error::Error err) {
57✔
150
                if (err != error::NoError) {
57✔
151
                        log::Error("Failed to submit inventory: " + err.String());
×
152
                        // Replace the inventory poll timer with a backoff
NEW
153
                        HandlePollingError(ctx, poster);
×
154
                        poster.PostEvent(StateEvent::Failure);
×
155
                        return;
×
156
                }
157
                retry_.backoff.Reset();
158
                ctx.inventory_client->has_submitted_inventory = true;
57✔
159
                poster.PostEvent(StateEvent::Success);
57✔
160
        };
57✔
161

162
        auto err = ctx.inventory_client->PushData(
163
                ctx.mender_context.GetConfig().paths.GetInventoryScriptsDir(),
57✔
164
                ctx.event_loop,
165
                ctx.http_client,
166
                handler);
57✔
167

168
        if (err != error::NoError) {
57✔
169
                // This is the only case the handler won't be called for us by
170
                // PushData() (see inventory::PushInventoryData()).
171
                handler(err);
×
172
        }
173
}
57✔
174

175
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
176
        // Schedule timer for next update first, so that long running submissions do not postpone
177
        // the schedule.
178
        log::Debug(
57✔
179
                "Scheduling the next inventory submission in: "
180
                + to_string(ctx.mender_context.GetConfig().inventory_poll_interval_seconds) + " seconds");
114✔
181
        retry_.wait_timer.AsyncWait(
57✔
182
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
57✔
183
                [&poster](error::Error err) {
2✔
184
                        if (err != error::NoError) {
1✔
185
                                if (err.code != make_error_condition(errc::operation_canceled)) {
×
186
                                        log::Error("Inventory poll timer caused error: " + err.String());
×
187
                                }
188
                        } else {
189
                                poster.PostEvent(StateEvent::InventoryPollingTriggered);
1✔
190
                        }
191
                });
1✔
192

193
        DoSubmitInventory(ctx, poster);
57✔
194
}
57✔
195

196
PollForDeploymentState::PollForDeploymentState(
94✔
197
        events::EventLoop &event_loop, int retry_interval_seconds, int retry_count) :
198
        retry_ {
199
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
200
                event_loop} {
188✔
201
}
94✔
202

203
void PollForDeploymentState::HandlePollingError(Context &ctx, sm::EventPoster<StateEvent> &poster) {
×
204
        // When using short polling inteervals, we should adjust the backoff to ensure
205
        // that the intervals do not exceed the maximum retry polling interval, which
206
        // converts the backoff to a fixed interval.
207
        chrono::milliseconds max_interval =
208
                chrono::seconds(ctx.mender_context.GetConfig().retry_poll_interval_seconds);
×
209
        if (max_interval < retry_.backoff.SmallestInterval()) {
×
210
                retry_.backoff.SetSmallestInterval(max_interval);
×
211
                retry_.backoff.SetMaxInterval(max_interval);
×
212
        }
213
        auto exp_interval = retry_.backoff.NextInterval();
×
214
        if (!exp_interval) {
×
215
                log::Debug(
×
216
                        "Not retrying with backoff, retrying with UpdatePollIntervalSeconds: "
217
                        + exp_interval.error().String());
×
218
                return;
219
        }
220
        log::Info(
×
221
                "Retrying deployment polling in "
222
                + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count()) + " seconds");
×
223

224
        retry_.wait_timer.Cancel();
×
225
        retry_.wait_timer.AsyncWait(*exp_interval, [&poster](error::Error err) {
×
226
                if (err != error::NoError) {
×
227
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
228
                                log::Error("Retry poll timer caused error: " + err.String());
×
229
                        }
230
                } else {
231
                        poster.PostEvent(StateEvent::DeploymentPollingTriggered);
×
232
                }
233
        });
×
234
}
235

236
void PollForDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
237
        log::Debug("Polling for update");
114✔
238

239
        // Schedule timer for next update first, so that long running submissions do not postpone
240
        // the schedule.
241
        log::Debug(
57✔
242
                "Scheduling the next deployment check in: "
243
                + to_string(ctx.mender_context.GetConfig().update_poll_interval_seconds) + " seconds");
114✔
244
        retry_.wait_timer.AsyncWait(
57✔
245
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
57✔
246
                [&poster](error::Error err) {
4✔
247
                        if (err != error::NoError) {
2✔
248
                                if (err.code != make_error_condition(errc::operation_canceled)) {
×
249
                                        log::Error("Update poll timer caused error: " + err.String());
×
250
                                }
251
                        } else {
252
                                poster.PostEvent(StateEvent::DeploymentPollingTriggered);
2✔
253
                        }
254
                });
59✔
255

256
        auto err = ctx.deployment_client->CheckNewDeployments(
257
                ctx.mender_context,
258
                ctx.http_client,
259
                [this, &ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
112✔
260
                        if (!response) {
56✔
261
                                log::Error("Error while polling for deployment: " + response.error().String());
×
262
                                // Replace the update poll timer with a backoff
NEW
263
                                HandlePollingError(ctx, poster);
×
UNCOV
264
                                poster.PostEvent(StateEvent::Failure);
×
265
                                return;
1✔
266
                        } else if (!response.value()) {
56✔
267
                                log::Info("No update available");
2✔
268
                                poster.PostEvent(StateEvent::NothingToDo);
1✔
269
                                if (not ctx.inventory_client->has_submitted_inventory) {
1✔
270
                                        // If we have not submitted inventory successfully at least
271
                                        // once, schedule this after receiving a successful response
272
                                        // with no update. This enables inventory to be submitted
273
                                        // immediately after the device has been accepted. If there
274
                                        // is an update available, an inventory update will be
275
                                        // scheduled at the end of it unconditionally.
276
                                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
277
                                }
278

279
                                retry_.backoff.Reset();
280
                                return;
1✔
281
                        }
282
                        retry_.backoff.Reset();
283

284
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
55✔
285
                        if (!exp_data) {
55✔
286
                                log::Error("Error in API response: " + exp_data.error().String());
×
287
                                poster.PostEvent(StateEvent::Failure);
×
288
                                return;
289
                        }
290

291
                        // Make a new set of update data.
292
                        ctx.deployment.state_data.reset(new StateData(std::move(exp_data.value())));
55✔
293

294
                        ctx.BeginDeploymentLogging();
55✔
295

296
                        log::Info("Running Mender client " + conf::kMenderVersion);
110✔
297
                        log::Info(
55✔
298
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
110✔
299

300
                        poster.PostEvent(StateEvent::DeploymentStarted);
55✔
301
                        poster.PostEvent(StateEvent::Success);
55✔
302
                });
57✔
303

304
        if (err != error::NoError) {
57✔
305
                log::Error("Error when trying to poll for deployment: " + err.String());
2✔
306
                poster.PostEvent(StateEvent::Failure);
1✔
307
        }
308
}
57✔
309

310
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
534✔
311
        assert(ctx.deployment.state_data);
312

313
        ctx.deployment.state_data->state = DatabaseStateString();
534✔
314

315
        log::Trace("Storing deployment state in the DB (database-string): " + DatabaseStateString());
1,068✔
316

317
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
534✔
318
        if (err != error::NoError) {
534✔
319
                log::Error(err.String());
10✔
320
                if (err.code
10✔
321
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
10✔
322
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
323
                        return;
324
                } else if (!IsFailureState()) {
9✔
325
                        // Non-failure states should be interrupted, but failure states should be
326
                        // allowed to do their work, even if a database error was detected.
327
                        poster.PostEvent(StateEvent::Failure);
2✔
328
                        return;
329
                }
330
        }
331

332
        OnEnterSaveState(ctx, poster);
531✔
333
}
334

335
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
53✔
336
        log::Debug("Entering Download state");
106✔
337

338
        auto req = make_shared<http::OutgoingRequest>();
53✔
339
        req->SetMethod(http::Method::GET);
53✔
340
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
53✔
341
        if (err != error::NoError) {
53✔
342
                log::Error(err.String());
×
343
                poster.PostEvent(StateEvent::Failure);
×
344
                return;
345
        }
346

347
        err = ctx.download_client->AsyncCall(
53✔
348
                req,
349
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
105✔
350
                        if (!exp_resp) {
53✔
351
                                log::Error("Unexpected error during download: " + exp_resp.error().String());
×
352
                                poster.PostEvent(StateEvent::Failure);
×
353
                                return;
1✔
354
                        }
355

356
                        auto &resp = exp_resp.value();
53✔
357
                        if (resp->GetStatusCode() != http::StatusOK) {
53✔
358
                                log::Error(
1✔
359
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
360
                                poster.PostEvent(StateEvent::Failure);
1✔
361
                                return;
1✔
362
                        }
363

364
                        auto http_reader = resp->MakeBodyAsyncReader();
52✔
365
                        if (!http_reader) {
52✔
366
                                log::Error(http_reader.error().String());
×
367
                                poster.PostEvent(StateEvent::Failure);
×
368
                                return;
369
                        }
370
                        ctx.deployment.artifact_reader =
371
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
52✔
372
                        ParseArtifact(ctx, poster);
52✔
373
                },
374
                [](http::ExpectedIncomingResponsePtr exp_resp) {
53✔
375
                        if (!exp_resp) {
53✔
376
                                log::Error(exp_resp.error().String());
6✔
377
                                // Cannot handle error here, because this handler is called at the
378
                                // end of the download, when we have already left this state. So
379
                                // rely on this error being propagated through the BodyAsyncReader
380
                                // above instead.
381
                                return;
6✔
382
                        }
383
                });
106✔
384

385
        if (err != error::NoError) {
53✔
386
                log::Error(err.String());
×
387
                poster.PostEvent(StateEvent::Failure);
×
388
                return;
389
        }
390
}
391

392
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
393
        string art_scripts_path = ctx.mender_context.GetConfig().paths.GetArtScriptsPath();
52✔
394

395
        // Clear the artifact scripts directory so we don't risk old scripts lingering.
396
        auto err = path::DeleteRecursively(art_scripts_path);
52✔
397
        if (err != error::NoError) {
52✔
398
                log::Error("When preparing to parse artifact: " + err.String());
×
399
                poster.PostEvent(StateEvent::Failure);
×
400
                return;
401
        }
402

403
        artifact::config::ParserConfig config {
52✔
404
                .artifact_scripts_filesystem_path = art_scripts_path,
405
                .artifact_scripts_version = 3,
406
                .artifact_verify_keys = ctx.mender_context.GetConfig().artifact_verify_keys,
52✔
407
        };
100✔
408
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
104✔
409
        if (!exp_parser) {
52✔
410
                log::Error(exp_parser.error().String());
×
411
                poster.PostEvent(StateEvent::Failure);
×
412
                return;
413
        }
414
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
52✔
415

416
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
52✔
417
        if (!exp_header) {
52✔
418
                log::Error(exp_header.error().String());
×
419
                poster.PostEvent(StateEvent::Failure);
×
420
                return;
421
        }
422
        auto &header = exp_header.value();
52✔
423

424
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
52✔
425
        if (!exp_matches) {
52✔
426
                log::Error(exp_matches.error().String());
2✔
427
                poster.PostEvent(StateEvent::Failure);
2✔
428
                return;
429
        } else if (!exp_matches.value()) {
50✔
430
                // reasons already logged
431
                poster.PostEvent(StateEvent::Failure);
1✔
432
                return;
433
        }
434

435
        log::Info("Installing artifact...");
98✔
436

437
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
49✔
438

439
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
49✔
440

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

443
        // Initial state data save, now that we have enough information from the artifact.
444
        err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
49✔
445
        if (err != error::NoError) {
49✔
446
                log::Error(err.String());
×
447
                if (err.code
×
448
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
449
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
450
                        return;
451
                } else {
452
                        poster.PostEvent(StateEvent::Failure);
×
453
                        return;
454
                }
455
        }
456

457
        if (header.header.payload_type == "") {
49✔
458
                // Empty-payload-artifact, aka "bootstrap artifact".
459
                poster.PostEvent(StateEvent::NothingToDo);
1✔
460
                return;
461
        }
462

463
        ctx.deployment.update_module.reset(
464
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
48✔
465

466
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
48✔
467
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
48✔
468
        if (err != error::NoError) {
48✔
469
                log::Error(err.String());
×
470
                poster.PostEvent(StateEvent::Failure);
×
471
                return;
472
        }
473

474
        err = ctx.deployment.update_module->AsyncProvidePayloadFileSizes(
48✔
475
                ctx.event_loop, [&ctx, &poster](expected::ExpectedBool download_with_sizes) {
48✔
476
                        if (!download_with_sizes.has_value()) {
48✔
477
                                log::Error(download_with_sizes.error().String());
×
478
                                poster.PostEvent(StateEvent::Failure);
×
479
                                return;
×
480
                        }
481
                        ctx.deployment.download_with_sizes = download_with_sizes.value();
48✔
482
                        DoDownload(ctx, poster);
48✔
483
                });
48✔
484

485
        if (err != error::NoError) {
48✔
486
                log::Error(err.String());
×
487
                poster.PostEvent(StateEvent::Failure);
×
488
                return;
489
        }
490
}
491

492
void UpdateDownloadState::DoDownload(Context &ctx, sm::EventPoster<StateEvent> &poster) {
48✔
493
        auto exp_payload = ctx.deployment.artifact_parser->Next();
48✔
494
        if (!exp_payload) {
48✔
495
                log::Error(exp_payload.error().String());
×
496
                poster.PostEvent(StateEvent::Failure);
×
497
                return;
498
        }
499
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
48✔
500

501
        auto handler = [&poster, &ctx](error::Error err) {
46✔
502
                if (err != error::NoError) {
48✔
503
                        log::Error(err.String());
2✔
504
                        poster.PostEvent(StateEvent::Failure);
2✔
505
                        return;
2✔
506
                }
507

508
                auto exp_payload = ctx.deployment.artifact_parser->Next();
46✔
509
                if (exp_payload) {
46✔
510
                        log::Error("Multiple payloads are not yet supported in daemon mode.");
×
511
                        poster.PostEvent(StateEvent::Failure);
×
512
                        return;
513
                } else if (
46✔
514
                        exp_payload.error().code
515
                        != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
46✔
516
                        log::Error(exp_payload.error().String());
×
517
                        poster.PostEvent(StateEvent::Failure);
×
518
                        return;
519
                }
520

521
                poster.PostEvent(StateEvent::Success);
46✔
522
        };
523

524
        if (ctx.deployment.download_with_sizes) {
48✔
525
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
1✔
526
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
1✔
527
        } else {
528
                ctx.deployment.update_module->AsyncDownload(
47✔
529
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
47✔
530
        }
531
}
532

533
void UpdateDownloadCancelState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
534
        log::Debug("Entering DownloadCancel state");
12✔
535
        ctx.download_client->Cancel();
6✔
536
        poster.PostEvent(StateEvent::Success);
6✔
537
}
6✔
538

539
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
×
540
        status_(status),
541
        mode_(FailureMode::Ignore) {
×
542
}
×
543

544
SendStatusUpdateState::SendStatusUpdateState(
188✔
545
        optional<deployments::DeploymentStatus> status,
546
        events::EventLoop &event_loop,
547
        int retry_interval_seconds,
548
        int retry_count) :
549
        status_(status),
550
        mode_(FailureMode::RetryThenFail),
551
        retry_(Retry {
188✔
552
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
553
                event_loop}) {
564✔
554
}
188✔
555

556
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
178✔
557
        if (retry_) {
178✔
558
                retry_->backoff.SetSmallestInterval(interval);
178✔
559
        }
560
}
178✔
561

562
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
242✔
563
        // Reset this every time we enter the state, which means a new round of retries.
564
        if (retry_) {
242✔
565
                retry_->backoff.Reset();
566
        }
567

568
        DoStatusUpdate(ctx, poster);
242✔
569
}
242✔
570

571
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
261✔
572
        assert(ctx.deployment_client);
573
        assert(ctx.deployment.state_data);
574

575
        log::Info("Sending status update to server");
522✔
576

577
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
566✔
578
                if (err != error::NoError) {
261✔
579
                        log::Error("Could not send deployment status: " + err.String());
48✔
580

581
                        switch (mode_) {
24✔
582
                        case FailureMode::Ignore:
583
                                break;
3✔
584
                        case FailureMode::RetryThenFail:
585
                                if (err.code
21✔
586
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
587
                                        // If the deployment was aborted upstream it is an immediate
588
                                        // failure, even if retry is enabled.
589
                                        poster.PostEvent(StateEvent::Failure);
1✔
590
                                        return;
21✔
591
                                }
592

593
                                auto exp_interval = retry_->backoff.NextInterval();
20✔
594
                                if (!exp_interval) {
20✔
595
                                        log::Error(
1✔
596
                                                "Giving up on sending status updates to server: "
597
                                                + exp_interval.error().String());
2✔
598
                                        poster.PostEvent(StateEvent::Failure);
1✔
599
                                        return;
600
                                }
601

602
                                log::Info(
19✔
603
                                        "Retrying status update after "
604
                                        + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count())
38✔
605
                                        + " seconds");
38✔
606
                                retry_->wait_timer.AsyncWait(
19✔
607
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
38✔
608
                                                // Error here is quite unexpected (from a timer), so treat
609
                                                // this as an immediate error, despite Retry flag.
610
                                                if (err != error::NoError) {
19✔
611
                                                        log::Error(
×
612
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
613
                                                                + err.String());
×
614
                                                        poster.PostEvent(StateEvent::Failure);
×
615
                                                        return;
×
616
                                                }
617

618
                                                // Try again. Since both status and logs are sent
619
                                                // from here, there's a chance this might resubmit
620
                                                // the status, but there's no harm in it, and it
621
                                                // won't happen often.
622
                                                DoStatusUpdate(ctx, poster);
19✔
623
                                        });
19✔
624
                                return;
19✔
625
                        }
626
                }
627

628
                poster.PostEvent(StateEvent::Success);
240✔
629
        };
261✔
630

631
        deployments::DeploymentStatus status;
632
        if (status_) {
261✔
633
                status = status_.value();
170✔
634
        } else {
635
                // If nothing is specified, grab success/failure status from the deployment status.
636
                if (ctx.deployment.failed) {
91✔
637
                        status = deployments::DeploymentStatus::Failure;
638
                } else {
639
                        status = deployments::DeploymentStatus::Success;
640
                }
641
        }
642

643
        // Push status.
644
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
522✔
645
        auto err = ctx.deployment_client->PushStatus(
646
                ctx.deployment.state_data->update_info.id,
261✔
647
                status,
648
                "",
649
                ctx.http_client,
650
                [result_handler, &ctx](error::Error err) {
73✔
651
                        // If there is an error, we don't submit logs now, but call the handler,
652
                        // which may schedule a retry later. If there is no error, and the
653
                        // deployment as a whole was successful, then also call the handler here,
654
                        // since we don't need to submit logs at all then.
655
                        if (err != error::NoError || !ctx.deployment.failed) {
261✔
656
                                result_handler(err);
188✔
657
                                return;
188✔
658
                        }
659

660
                        // Push logs.
661
                        err = ctx.deployment_client->PushLogs(
73✔
662
                                ctx.deployment.state_data->update_info.id,
73✔
663
                                ctx.deployment.logger->LogFilePath(),
146✔
664
                                ctx.http_client,
665
                                result_handler);
73✔
666

667
                        if (err != error::NoError) {
73✔
668
                                result_handler(err);
×
669
                        }
670
                });
522✔
671

672
        if (err != error::NoError) {
261✔
673
                result_handler(err);
×
674
        }
675

676
        // No action, wait for reply from status endpoint.
677
}
261✔
678

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

682
        DefaultAsyncErrorHandler(
42✔
683
                poster,
684
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
685
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
686
}
42✔
687

688
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
689
        DefaultAsyncErrorHandler(
73✔
690
                poster,
691
                ctx.deployment.update_module->AsyncNeedsReboot(
73✔
692
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
144✔
693
                                if (!reboot_action.has_value()) {
73✔
694
                                        log::Error(reboot_action.error().String());
2✔
695
                                        poster.PostEvent(StateEvent::Failure);
2✔
696
                                        return;
2✔
697
                                }
698

699
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
71✔
700
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
701
                                        NeedsRebootToDbString(*reboot_action);
71✔
702
                                switch (*reboot_action) {
71✔
703
                                case update_module::RebootAction::No:
8✔
704
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
705
                                        break;
8✔
706
                                case update_module::RebootAction::Yes:
63✔
707
                                case update_module::RebootAction::Automatic:
708
                                        poster.PostEvent(StateEvent::Success);
63✔
709
                                        break;
63✔
710
                                }
711
                        }));
73✔
712
}
73✔
713

714
void UpdateRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
27✔
715
        log::Debug("Entering ArtifactReboot state");
54✔
716

717
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
718
        auto exp_reboot_mode =
719
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
27✔
720
        // Should always be true because we check it at load time.
721
        assert(exp_reboot_mode);
722

723
        switch (exp_reboot_mode.value()) {
27✔
724
        case update_module::RebootAction::No:
×
725
                // Should not happen because then we don't enter this state.
726
                assert(false);
727
                poster.PostEvent(StateEvent::Failure);
×
728
                break;
729
        case update_module::RebootAction::Yes:
27✔
730
                DefaultAsyncErrorHandler(
27✔
731
                        poster,
732
                        ctx.deployment.update_module->AsyncArtifactReboot(
27✔
733
                                ctx.event_loop, DefaultStateHandler {poster}));
27✔
734
                break;
27✔
735
        case update_module::RebootAction::Automatic:
×
736
                DefaultAsyncErrorHandler(
×
737
                        poster,
738
                        ctx.deployment.update_module->AsyncSystemReboot(
×
739
                                ctx.event_loop, DefaultStateHandler {poster}));
×
740
                break;
×
741
        }
742
}
27✔
743

744
void UpdateVerifyRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
30✔
745
        log::Debug("Entering ArtifactVerifyReboot state");
60✔
746

747
        ctx.deployment.update_module->EnsureRootfsImageFileTree(
30✔
748
                ctx.deployment.update_module->GetUpdateModuleWorkDir());
60✔
749

750
        DefaultAsyncErrorHandler(
30✔
751
                poster,
752
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
30✔
753
                        ctx.event_loop, DefaultStateHandler {poster}));
30✔
754
}
30✔
755

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

761
        poster.PostEvent(StateEvent::Success);
23✔
762
}
23✔
763

764
void UpdateCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
765
        log::Debug("Entering ArtifactCommit state");
38✔
766

767
        // Explicitly check if state scripts version is supported
768
        auto err = script_executor::CheckScriptsCompatibility(
769
                ctx.mender_context.GetConfig().paths.GetRootfsScriptsPath());
19✔
770
        if (err != error::NoError) {
19✔
771
                log::Error("Failed script compatibility check: " + err.String());
×
772
                poster.PostEvent(StateEvent::Failure);
×
773
                return;
774
        }
775

776
        DefaultAsyncErrorHandler(
19✔
777
                poster,
778
                ctx.deployment.update_module->AsyncArtifactCommit(
19✔
779
                        ctx.event_loop, DefaultStateHandler {poster}));
38✔
780
}
781

782
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
783
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
784
        assert(ctx.deployment.state_data);
785
        auto &state_data = *ctx.deployment.state_data;
786
        if (state_data.update_info.has_db_schema_update) {
19✔
787
                state_data.update_info.has_db_schema_update = false;
×
788
                auto err = ctx.SaveDeploymentStateData(state_data);
×
789
                if (err != error::NoError) {
×
790
                        log::Error("Not able to commit schema update: " + err.String());
×
791
                        poster.PostEvent(StateEvent::Failure);
×
792
                        return;
793
                }
794
        }
795

796
        poster.PostEvent(StateEvent::Success);
19✔
797
}
798

799
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
45✔
800
        DefaultAsyncErrorHandler(
45✔
801
                poster,
802
                ctx.deployment.update_module->AsyncSupportsRollback(
45✔
803
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
89✔
804
                                if (!rollback_supported.has_value()) {
45✔
805
                                        log::Error(rollback_supported.error().String());
1✔
806
                                        poster.PostEvent(StateEvent::Failure);
1✔
807
                                        return;
1✔
808
                                }
809

810
                                ctx.deployment.state_data->update_info.supports_rollback =
811
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
812
                                if (*rollback_supported) {
44✔
813
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
814
                                        poster.PostEvent(StateEvent::Success);
38✔
815
                                } else {
816
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
817
                                }
818
                        }));
45✔
819
}
45✔
820

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

824
        DefaultAsyncErrorHandler(
41✔
825
                poster,
826
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
827
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
828
}
41✔
829

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

833
        auto exp_reboot_mode =
834
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
57✔
835
        // Should always be true because we check it at load time.
836
        assert(exp_reboot_mode);
837

838
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
839
        // succeeds.
840
        auto handler = [&poster](error::Error err) {
114✔
841
                if (err != error::NoError) {
57✔
842
                        log::Error(err.String());
2✔
843
                }
844
                poster.PostEvent(StateEvent::Success);
57✔
845
        };
57✔
846

847
        error::Error err;
57✔
848
        switch (exp_reboot_mode.value()) {
57✔
849
        case update_module::RebootAction::No:
850
                // Should not happen because then we don't enter this state.
851
                assert(false);
852

853
                err = error::MakeError(
×
854
                        error::ProgrammingError, "Entered UpdateRollbackRebootState with RebootAction = No");
×
855
                break;
×
856

857
        case update_module::RebootAction::Yes:
57✔
858
                err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(ctx.event_loop, handler);
114✔
859
                break;
57✔
860

861
        case update_module::RebootAction::Automatic:
×
862
                err = ctx.deployment.update_module->AsyncSystemReboot(ctx.event_loop, handler);
×
863
                break;
×
864
        }
865

866
        if (err != error::NoError) {
57✔
867
                log::Error(err.String());
×
868
                poster.PostEvent(StateEvent::Success);
×
869
        }
870
}
57✔
871

872
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
873
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
874
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
875

876
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
877
        // state loop detection will eventually kick in.
878
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
879
                ctx.event_loop, [&poster](error::Error err) {
120✔
880
                        if (err != error::NoError) {
60✔
881
                                log::Error(err.String());
22✔
882
                                poster.PostEvent(StateEvent::Retry);
22✔
883
                                return;
22✔
884
                        }
885
                        poster.PostEvent(StateEvent::Success);
38✔
886
                });
60✔
887
        if (err != error::NoError) {
60✔
888
                log::Error(err.String());
×
889
                poster.PostEvent(StateEvent::Retry);
×
890
        }
891
}
60✔
892

893
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
894
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
895
        poster.PostEvent(StateEvent::Success);
50✔
896
}
50✔
897

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

901
        DefaultAsyncErrorHandler(
55✔
902
                poster,
903
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
904
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
905
}
55✔
906

907
static string AddInconsistentSuffix(const string &str) {
21✔
908
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
909
        // `string::ends_with` is C++20... grumble
910
        string ret {str};
21✔
911
        if (!common::EndsWith(ret, suffix)) {
21✔
912
                ret.append(suffix);
21✔
913
        }
914
        return ret;
21✔
915
}
916

917
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
918
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
919
                // If the update failed, but we rolled back successfully, then we don't need to do
920
                // anything, just keep the old data.
921
                poster.PostEvent(StateEvent::Success);
38✔
922
                return;
38✔
923
        }
924

925
        assert(ctx.deployment.state_data);
926
        // This state should never happen: rollback failed, but update not failed??
927
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
928

929
        // We expect Cleanup to be the next state after this.
930
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
37✔
931

932
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
933

934
        string artifact_name;
935
        if (ctx.deployment.rollback_failed) {
37✔
936
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
38✔
937
        } else {
938
                artifact_name = artifact.artifact_name;
18✔
939
        }
940

941
        bool deploy_failed = ctx.deployment.failed;
37✔
942

943
        // Only the artifact_name and group should be committed in the case of a
944
        // failing update in order to make this consistent with the old client
945
        // behaviour.
946
        auto err = ctx.mender_context.CommitArtifactData(
37✔
947
                artifact_name,
948
                artifact.artifact_group,
37✔
949
                deploy_failed ? nullopt : optional<context::ProvidesData>(artifact.type_info_provides),
74✔
950
                /* Special case: Keep existing provides */
951
                deploy_failed ? context::ClearsProvidesData {}
93✔
952
                                          : optional<context::ClearsProvidesData>(artifact.clears_artifact_provides),
18✔
953
                [&ctx](kv_db::Transaction &txn) {
37✔
954
                        // Save the Cleanup state together with the artifact data, atomically.
955
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
37✔
956
                });
74✔
957
        if (err != error::NoError) {
37✔
958
                log::Error("Error saving artifact data: " + err.String());
×
959
                if (err.code
×
960
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
961
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
962
                        return;
963
                }
964
                poster.PostEvent(StateEvent::Failure);
×
965
                return;
966
        }
967

968
        poster.PostEvent(StateEvent::Success);
37✔
969
}
970

971
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
89✔
972
        log::Debug("Entering ArtifactCleanup state");
178✔
973

974
        // It's possible for there not to be an initialized update_module structure, if the
975
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
976
        // no-op.
977
        if (!ctx.deployment.update_module) {
89✔
978
                poster.PostEvent(StateEvent::Success);
8✔
979
                return;
8✔
980
        }
981

982
        DefaultAsyncErrorHandler(
81✔
983
                poster,
984
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
162✔
985
}
986

987
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
988
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
91✔
989
                // Remove state data, since we're done now.
990
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
89✔
991
                if (err != error::NoError) {
89✔
992
                        return err;
×
993
                }
994
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
89✔
995
        });
91✔
996
        if (err != error::NoError) {
91✔
997
                log::Error("Error removing artifact data: " + err.String());
4✔
998
                poster.PostEvent(StateEvent::Failure);
2✔
999
                return;
1000
        }
1001

1002
        poster.PostEvent(StateEvent::Success);
89✔
1003
}
1004

1005
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
1006
        assert(ctx.deployment.state_data);
1007
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
1008

1009
        // Mark update as inconsistent.
1010
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
1011

1012
        auto err = ctx.mender_context.CommitArtifactData(
2✔
1013
                artifact_name,
1014
                artifact.artifact_group,
2✔
1015
                artifact.type_info_provides,
2✔
1016
                artifact.clears_artifact_provides,
2✔
1017
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
1018
        if (err != error::NoError) {
2✔
1019
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
1020
                poster.PostEvent(StateEvent::Failure);
×
1021
                return;
1022
        }
1023

1024
        poster.PostEvent(StateEvent::Success);
2✔
1025
}
1026

1027
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
1028
        log::Info(
91✔
1029
                "Deployment with ID " + ctx.deployment.state_data->update_info.id
182✔
1030
                + " finished with status: " + string(ctx.deployment.failed ? "Failure" : "Success"));
382✔
1031

1032
        ctx.FinishDeploymentLogging();
91✔
1033

1034
        ctx.deployment = {};
91✔
1035
        poster.PostEvent(
91✔
1036
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
91✔
1037
        poster.PostEvent(StateEvent::DeploymentEnded);
91✔
1038
        poster.PostEvent(StateEvent::Success);
91✔
1039
}
91✔
1040

1041
ExitState::ExitState(events::EventLoop &event_loop) :
94✔
1042
        event_loop_(event_loop) {
188✔
1043
}
94✔
1044

1045
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
1046
#ifndef NDEBUG
1047
        if (--iterations_left_ <= 0) {
1048
                event_loop_.Stop();
1049
        } else {
1050
                poster.PostEvent(StateEvent::Success);
1051
        }
1052
#else
1053
        event_loop_.Stop();
91✔
1054
#endif
1055
}
91✔
1056

1057
namespace deployment_tracking {
1058

1059
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
61✔
1060
        ctx.deployment.failed = false;
61✔
1061
        ctx.deployment.rollback_failed = false;
61✔
1062
}
61✔
1063

1064
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
58✔
1065
        ctx.deployment.failed = true;
58✔
1066
        ctx.deployment.rollback_failed = true;
58✔
1067
}
58✔
1068

1069
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
1070
        ctx.deployment.failed = true;
52✔
1071
        ctx.deployment.rollback_failed = false;
52✔
1072
}
52✔
1073

1074
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
1075
        ctx.deployment.failed = true;
12✔
1076
        ctx.deployment.rollback_failed = true;
12✔
1077
}
12✔
1078

1079
} // namespace deployment_tracking
1080

1081
} // namespace daemon
1082
} // namespace update
1083
} // 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