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

mendersoftware / mender / 2276543477

21 Jan 2026 01:00PM UTC coverage: 79.764%. Remained the same
2276543477

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

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

97 existing lines in 2 files now uncovered.

7919 of 9928 relevant lines covered (79.76%)

13839.69 hits per line

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

79.51
/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...");
2,128✔
72
        auto err = this->script_.AsyncRunScripts(
73
                this->state_,
74
                this->action_,
75
                [state_name, &poster](error::Error err) {
7,881✔
76
                        if (err != error::NoError) {
1,064✔
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,086✔
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");
246✔
104
}
123✔
105

106
ScheduleNextPollState::ScheduleNextPollState(
×
107
        events::Timer &timer, const string &poll_action, const StateEvent event, int interval) :
108
        timer_ {timer},
109
        poll_action_ {poll_action},
110
        event_ {event},
111
        interval_ {interval} {
×
112
}
×
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) :
96✔
130
        backoff_ {chrono::seconds(retry_interval_seconds), retry_count} {
192✔
131
}
96✔
132

133
void SubmitInventoryState::HandlePollingError(Context &ctx, sm::EventPoster<StateEvent> &poster) {
×
134
        // When using short polling intervals, we should adjust the backoff to ensure
135
        // that the intervals do not exceed the maximum retry polling interval, which
136
        // converts the backoff to a fixed interval.
137
        chrono::milliseconds max_interval =
138
                chrono::seconds(ctx.mender_context.GetConfig().retry_poll_interval_seconds);
×
139
        if (max_interval < backoff_.SmallestInterval()) {
×
140
                backoff_.SetSmallestInterval(max_interval);
×
141
                backoff_.SetMaxInterval(max_interval);
×
142
        }
143
        auto exp_interval = backoff_.NextInterval();
×
144
        if (!exp_interval) {
×
145
                log::Debug(
×
146
                        "Not retrying with backoff, retrying InventoryPollIntervalSeconds: "
147
                        + exp_interval.error().String());
×
148
                return;
149
        }
150
        log::Info(
×
151
                "Retrying inventory polling in "
152
                + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count()) + " seconds");
×
153

154
        ctx.inventory_timer.Cancel();
×
155
        ctx.inventory_timer.AsyncWait(*exp_interval, [&poster](error::Error err) {
×
156
                if (err != error::NoError) {
×
157
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
158
                                log::Error("Retry poll timer caused error: " + err.String());
×
159
                        }
160
                } else {
161
                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
162
                }
163
        });
×
164
}
165

166
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
59✔
167
        log::Debug("Submitting inventory");
118✔
168

169
        auto handler = [this, &ctx, &poster](error::Error err) {
59✔
170
                if (err != error::NoError) {
59✔
171
                        log::Error("Failed to submit inventory: " + err.String());
×
172
                        // Replace the inventory poll timer with a backoff
173
                        HandlePollingError(ctx, poster);
×
174
                        poster.PostEvent(StateEvent::Failure);
×
175
                        return;
×
176
                }
177
                backoff_.Reset();
178
                ctx.inventory_client->has_submitted_inventory = true;
59✔
179
                poster.PostEvent(StateEvent::Success);
59✔
180
        };
59✔
181

182
        auto err = ctx.inventory_client->PushData(
183
                ctx.mender_context.GetConfig().paths.GetInventoryScriptsDir(),
59✔
184
                ctx.event_loop,
185
                ctx.http_client,
186
                handler);
59✔
187

188
        if (err != error::NoError) {
59✔
189
                // This is the only case the handler won't be called for us by
190
                // PushData() (see inventory::PushInventoryData()).
191
                handler(err);
×
192
        }
193
}
59✔
194

195
PollForDeploymentState::PollForDeploymentState(int retry_interval_seconds, int retry_count) :
96✔
196
        backoff_ {chrono::seconds(retry_interval_seconds), retry_count} {
192✔
197
}
96✔
198

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

220
        ctx.deployment_timer.Cancel();
3✔
221
        ctx.deployment_timer.AsyncWait(*exp_interval, [&poster](error::Error err) {
3✔
222
                if (err != error::NoError) {
3✔
223
                        if (err.code != make_error_condition(errc::operation_canceled)) {
×
224
                                log::Error("Retry poll timer caused error: " + err.String());
×
225
                        }
226
                } else {
227
                        poster.PostEvent(StateEvent::DeploymentPollingTriggered);
3✔
228
                }
229
        });
6✔
230
}
231

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

235
        auto err = ctx.deployment_client->CheckNewDeployments(
236
                ctx.mender_context,
237
                ctx.http_client,
238
                [this, &ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
116✔
239
                        if (!response) {
58✔
240
                                log::Error("Error while polling for deployment: " + response.error().String());
×
241
                                // Replace the update poll timer with a backoff
242
                                HandlePollingError(ctx, poster);
×
243
                                poster.PostEvent(StateEvent::Failure);
×
244
                                return;
1✔
245
                        } else if (!response.value()) {
58✔
246
                                log::Info("No update available");
2✔
247
                                poster.PostEvent(StateEvent::NothingToDo);
1✔
248
                                if (not ctx.inventory_client->has_submitted_inventory) {
1✔
249
                                        // If we have not submitted inventory successfully at least
250
                                        // once, schedule this after receiving a successful response
251
                                        // with no update. This enables inventory to be submitted
252
                                        // immediately after the device has been accepted. If there
253
                                        // is an update available, an inventory update will be
254
                                        // scheduled at the end of it unconditionally.
255
                                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
256
                                }
257

258
                                backoff_.Reset();
259
                                return;
1✔
260
                        }
261
                        backoff_.Reset();
262

263
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
57✔
264
                        if (!exp_data) {
57✔
265
                                log::Error("Error in API response: " + exp_data.error().String());
×
266
                                poster.PostEvent(StateEvent::Failure);
×
267
                                return;
268
                        }
269

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

273
                        ctx.BeginDeploymentLogging();
57✔
274

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

281
                        poster.PostEvent(StateEvent::DeploymentStarted);
57✔
282
                        poster.PostEvent(StateEvent::Success);
57✔
283
                });
62✔
284

285
        if (err != error::NoError) {
62✔
286
                log::Error("Error when trying to poll for deployment: " + err.String());
8✔
287
                // If we're here, no handler will be called, so we need to manually schedule the next
288
                // deployment poll
289
                HandlePollingError(ctx, poster);
4✔
290
                poster.PostEvent(StateEvent::Failure);
4✔
291
        }
292
}
62✔
293

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

297
        ctx.deployment.state_data->state = DatabaseStateString();
535✔
298

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

301
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
535✔
302
        if (err != error::NoError) {
535✔
303
                log::Error(err.String());
10✔
304
                if (err.code
10✔
305
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
10✔
306
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
307
                        return;
308
                } else if (!IsFailureState()) {
9✔
309
                        // Non-failure states should be interrupted, but failure states should be
310
                        // allowed to do their work, even if a database error was detected.
311
                        poster.PostEvent(StateEvent::Failure);
2✔
312
                        return;
313
                }
314
        }
315

316
        OnEnterSaveState(ctx, poster);
532✔
317
}
318

319
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
320
        log::Debug("Entering Download state");
108✔
321

322
        auto req = make_shared<http::OutgoingRequest>();
54✔
323
        req->SetMethod(http::Method::GET);
54✔
324
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
54✔
325
        if (err != error::NoError) {
54✔
UNCOV
326
                log::Error(err.String());
×
UNCOV
327
                poster.PostEvent(StateEvent::Failure);
×
328
                return;
329
        }
330

331
        err = ctx.download_client->AsyncCall(
54✔
332
                req,
333
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
107✔
334
                        if (!exp_resp) {
54✔
UNCOV
335
                                log::Error("Unexpected error during download: " + exp_resp.error().String());
×
UNCOV
336
                                poster.PostEvent(StateEvent::Failure);
×
337
                                return;
1✔
338
                        }
339

340
                        auto &resp = exp_resp.value();
54✔
341
                        if (resp->GetStatusCode() != http::StatusOK) {
54✔
342
                                log::Error(
1✔
343
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
344
                                poster.PostEvent(StateEvent::Failure);
1✔
345
                                return;
1✔
346
                        }
347

348
                        auto http_reader = resp->MakeBodyAsyncReader();
53✔
349
                        if (!http_reader) {
53✔
UNCOV
350
                                log::Error(http_reader.error().String());
×
UNCOV
351
                                poster.PostEvent(StateEvent::Failure);
×
352
                                return;
353
                        }
354
                        ctx.deployment.artifact_reader =
355
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
53✔
356
                        ParseArtifact(ctx, poster);
53✔
357
                },
358
                [](http::ExpectedIncomingResponsePtr exp_resp) {
54✔
359
                        if (!exp_resp) {
54✔
360
                                log::Error(exp_resp.error().String());
6✔
361
                                // Cannot handle error here, because this handler is called at the
362
                                // end of the download, when we have already left this state. So
363
                                // rely on this error being propagated through the BodyAsyncReader
364
                                // above instead.
365
                                return;
6✔
366
                        }
367
                });
108✔
368

369
        if (err != error::NoError) {
54✔
UNCOV
370
                log::Error(err.String());
×
UNCOV
371
                poster.PostEvent(StateEvent::Failure);
×
372
                return;
373
        }
374
}
375

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

379
        // Clear the artifact scripts directory so we don't risk old scripts lingering.
380
        auto err = path::DeleteRecursively(art_scripts_path);
53✔
381
        if (err != error::NoError) {
53✔
UNCOV
382
                log::Error("When preparing to parse artifact: " + err.String());
×
UNCOV
383
                poster.PostEvent(StateEvent::Failure);
×
384
                return;
385
        }
386

387
        artifact::config::ParserConfig config {
53✔
388
                .artifact_scripts_filesystem_path = art_scripts_path,
389
                .artifact_scripts_version = 3,
390
                .artifact_verify_keys = ctx.mender_context.GetConfig().artifact_verify_keys,
53✔
391
        };
102✔
392
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
106✔
393
        if (!exp_parser) {
53✔
UNCOV
394
                log::Error(exp_parser.error().String());
×
UNCOV
395
                poster.PostEvent(StateEvent::Failure);
×
396
                return;
397
        }
398
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
53✔
399

400
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
53✔
401
        if (!exp_header) {
53✔
UNCOV
402
                log::Error(exp_header.error().String());
×
UNCOV
403
                poster.PostEvent(StateEvent::Failure);
×
404
                return;
405
        }
406
        auto &header = exp_header.value();
53✔
407

408
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
53✔
409
        if (!exp_matches) {
53✔
410
                log::Error(exp_matches.error().String());
2✔
411
                poster.PostEvent(StateEvent::Failure);
2✔
412
                return;
413
        } else if (!exp_matches.value()) {
51✔
414
                // reasons already logged
415
                poster.PostEvent(StateEvent::Failure);
1✔
416
                return;
417
        }
418

419
        log::Info("Installing artifact...");
100✔
420

421
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
50✔
422

423
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
50✔
424

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

427
        // Initial state data save, now that we have enough information from the artifact.
428
        err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
50✔
429
        if (err != error::NoError) {
50✔
UNCOV
430
                log::Error(err.String());
×
UNCOV
431
                if (err.code
×
UNCOV
432
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
433
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
434
                        return;
435
                } else {
436
                        poster.PostEvent(StateEvent::Failure);
×
437
                        return;
438
                }
439
        }
440

441
        if (header.header.payload_type == "") {
50✔
442
                // Empty-payload-artifact, aka "bootstrap artifact".
443
                poster.PostEvent(StateEvent::NothingToDo);
1✔
444
                return;
445
        }
446

447
        auto exp_update_module =
448
                update_module::UpdateModule::Create(ctx.mender_context, header.header.payload_type);
49✔
449
        if (!exp_update_module.has_value()) {
49✔
UNCOV
450
                log::Error(
×
451
                        "Error creating an Update Module when parsing artifact: "
UNCOV
452
                        + exp_update_module.error().String());
×
453
                poster.PostEvent(StateEvent::Failure);
×
454
                return;
455
        }
456
        ctx.deployment.update_module = std::move(exp_update_module.value());
49✔
457

458
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
49✔
459
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
49✔
460
        if (err != error::NoError) {
49✔
UNCOV
461
                log::Error(err.String());
×
UNCOV
462
                poster.PostEvent(StateEvent::Failure);
×
463
                return;
464
        }
465

466
        err = ctx.deployment.update_module->AsyncProvidePayloadFileSizes(
49✔
467
                ctx.event_loop, [&ctx, &poster](expected::ExpectedBool download_with_sizes) {
49✔
468
                        if (!download_with_sizes.has_value()) {
49✔
UNCOV
469
                                log::Error(download_with_sizes.error().String());
×
UNCOV
470
                                poster.PostEvent(StateEvent::Failure);
×
UNCOV
471
                                return;
×
472
                        }
473
                        ctx.deployment.download_with_sizes = download_with_sizes.value();
49✔
474
                        DoDownload(ctx, poster);
49✔
475
                });
49✔
476

477
        if (err != error::NoError) {
49✔
UNCOV
478
                log::Error(err.String());
×
UNCOV
479
                poster.PostEvent(StateEvent::Failure);
×
480
                return;
481
        }
482
}
483

484
void UpdateDownloadState::DoDownload(Context &ctx, sm::EventPoster<StateEvent> &poster) {
49✔
485
        auto exp_payload = ctx.deployment.artifact_parser->Next();
49✔
486
        if (!exp_payload) {
49✔
UNCOV
487
                log::Error(exp_payload.error().String());
×
UNCOV
488
                poster.PostEvent(StateEvent::Failure);
×
489
                return;
490
        }
491
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
49✔
492

493
        auto handler = [&poster, &ctx](error::Error err) {
47✔
494
                if (err != error::NoError) {
49✔
495
                        log::Error(err.String());
2✔
496
                        poster.PostEvent(StateEvent::Failure);
2✔
497
                        return;
2✔
498
                }
499

500
                auto exp_payload = ctx.deployment.artifact_parser->Next();
47✔
501
                if (exp_payload) {
47✔
UNCOV
502
                        log::Error("Multiple payloads are not yet supported in daemon mode.");
×
UNCOV
503
                        poster.PostEvent(StateEvent::Failure);
×
504
                        return;
505
                } else if (
47✔
506
                        exp_payload.error().code
507
                        != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
47✔
UNCOV
508
                        log::Error(exp_payload.error().String());
×
UNCOV
509
                        poster.PostEvent(StateEvent::Failure);
×
510
                        return;
511
                }
512

513
                poster.PostEvent(StateEvent::Success);
47✔
514
        };
515

516
        if (ctx.deployment.download_with_sizes) {
49✔
517
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
1✔
518
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
1✔
519
        } else {
520
                ctx.deployment.update_module->AsyncDownload(
48✔
521
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
48✔
522
        }
523
}
524

525
void UpdateDownloadCancelState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
526
        log::Debug("Entering DownloadCancel state");
12✔
527
        ctx.download_client->Cancel();
6✔
528
        poster.PostEvent(StateEvent::Success);
6✔
529
}
6✔
530

UNCOV
531
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
×
532
        status_(status),
UNCOV
533
        mode_(FailureMode::Ignore) {
×
534
}
×
535

536
SendStatusUpdateState::SendStatusUpdateState(
192✔
537
        optional<deployments::DeploymentStatus> status,
538
        events::EventLoop &event_loop,
539
        int retry_interval_seconds,
540
        int retry_count) :
541
        status_(status),
542
        mode_(FailureMode::RetryThenFail),
543
        retry_(Retry {
192✔
544
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
545
                event_loop}) {
576✔
546
}
192✔
547

548
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
182✔
549
        if (retry_) {
182✔
550
                retry_->backoff.SetSmallestInterval(interval);
182✔
551
        }
552
}
182✔
553

554
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
246✔
555
        // Reset this every time we enter the state, which means a new round of retries.
556
        if (retry_) {
246✔
557
                retry_->backoff.Reset();
558
        }
559

560
        DoStatusUpdate(ctx, poster);
246✔
561
}
246✔
562

563
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
265✔
564
        assert(ctx.deployment_client);
565
        assert(ctx.deployment.state_data);
566

567
        log::Info("Sending status update to server");
530✔
568

569
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
552✔
570
                if (err != error::NoError) {
265✔
571
                        log::Error("Could not send deployment status: " + err.String());
50✔
572

573
                        if (err.code == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
25✔
574
                                // If the deployment was aborted upstream it is an immediate
575
                                // failure, even if retry is enabled.
576
                                poster.PostEvent(StateEvent::DeploymentAborted);
3✔
577
                                return;
3✔
578
                        }
579

580
                        switch (mode_) {
22✔
581
                        case FailureMode::Ignore:
582
                                break;
2✔
583
                        case FailureMode::RetryThenFail:
20✔
584

585
                                auto exp_interval = retry_->backoff.NextInterval();
20✔
586
                                if (!exp_interval) {
20✔
587
                                        log::Error(
1✔
588
                                                "Giving up on sending status updates to server: "
589
                                                + exp_interval.error().String());
2✔
590
                                        poster.PostEvent(StateEvent::Failure);
1✔
591
                                        return;
592
                                }
593

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

610
                                                // Try again. Since both status and logs are sent
611
                                                // from here, there's a chance this might resubmit
612
                                                // the status, but there's no harm in it, and it
613
                                                // won't happen often.
614
                                                DoStatusUpdate(ctx, poster);
19✔
615
                                        });
19✔
616
                                return;
19✔
617
                        }
618
                }
619

620
                poster.PostEvent(StateEvent::Success);
242✔
621
        };
265✔
622

623
        deployments::DeploymentStatus status;
624
        if (status_) {
265✔
625
                status = status_.value();
172✔
626
        } else {
627
                // If nothing is specified, grab success/failure status from the deployment status.
628
                if (ctx.deployment.failed) {
93✔
629
                        status = deployments::DeploymentStatus::Failure;
630
                } else {
631
                        status = deployments::DeploymentStatus::Success;
632
                }
633
        }
634

635
        // Push status.
636
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
530✔
637
        auto err = ctx.deployment_client->PushStatus(
638
                ctx.deployment.state_data->update_info.id,
265✔
639
                status,
640
                "",
641
                ctx.http_client,
642
                [result_handler, &ctx](error::Error err) {
75✔
643
                        // If there is an error, we don't submit logs now, but call the handler,
644
                        // which may schedule a retry later. If there is no error, and the
645
                        // deployment as a whole was successful, then also call the handler here,
646
                        // since we don't need to submit logs at all then.
647
                        if (err != error::NoError || !ctx.deployment.failed) {
265✔
648
                                result_handler(err);
190✔
649
                                return;
190✔
650
                        }
651

652
                        // Push logs.
653
                        err = ctx.deployment_client->PushLogs(
75✔
654
                                ctx.deployment.state_data->update_info.id,
75✔
655
                                ctx.deployment.logger->LogFilePath(),
150✔
656
                                ctx.http_client,
657
                                result_handler);
75✔
658

659
                        if (err != error::NoError) {
75✔
UNCOV
660
                                result_handler(err);
×
661
                        }
662
                });
530✔
663

664
        if (err != error::NoError) {
265✔
UNCOV
665
                result_handler(err);
×
666
        }
667

668
        // No action, wait for reply from status endpoint.
669
}
265✔
670

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

674
        DefaultAsyncErrorHandler(
42✔
675
                poster,
676
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
677
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
678
}
42✔
679

680
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
681
        DefaultAsyncErrorHandler(
73✔
682
                poster,
683
                ctx.deployment.update_module->AsyncNeedsReboot(
73✔
684
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
144✔
685
                                if (!reboot_action.has_value()) {
73✔
686
                                        log::Error(reboot_action.error().String());
2✔
687
                                        poster.PostEvent(StateEvent::Failure);
2✔
688
                                        return;
2✔
689
                                }
690

691
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
71✔
692
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
693
                                        NeedsRebootToDbString(*reboot_action);
71✔
694
                                switch (*reboot_action) {
71✔
695
                                case update_module::RebootAction::No:
8✔
696
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
697
                                        break;
8✔
698
                                case update_module::RebootAction::Yes:
63✔
699
                                case update_module::RebootAction::Automatic:
700
                                        poster.PostEvent(StateEvent::Success);
63✔
701
                                        break;
63✔
702
                                }
703
                        }));
73✔
704
}
73✔
705

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

709
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
710
        auto exp_reboot_mode =
711
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
26✔
712
        // Should always be true because we check it at load time.
713
        assert(exp_reboot_mode);
714

715
        switch (exp_reboot_mode.value()) {
26✔
UNCOV
716
        case update_module::RebootAction::No:
×
717
                // Should not happen because then we don't enter this state.
718
                assert(false);
719
                poster.PostEvent(StateEvent::Failure);
×
720
                break;
721
        case update_module::RebootAction::Yes:
26✔
722
                DefaultAsyncErrorHandler(
26✔
723
                        poster,
724
                        ctx.deployment.update_module->AsyncArtifactReboot(
26✔
725
                                ctx.event_loop, DefaultStateHandler {poster}));
26✔
726
                break;
26✔
UNCOV
727
        case update_module::RebootAction::Automatic:
×
UNCOV
728
                DefaultAsyncErrorHandler(
×
729
                        poster,
730
                        ctx.deployment.update_module->AsyncSystemReboot(
×
731
                                ctx.event_loop, DefaultStateHandler {poster}));
×
UNCOV
732
                break;
×
733
        }
734
}
26✔
735

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

739
        ctx.deployment.update_module->EnsureRootfsImageFileTree(
29✔
740
                ctx.deployment.update_module->GetUpdateModuleWorkDir());
58✔
741

742
        DefaultAsyncErrorHandler(
29✔
743
                poster,
744
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
29✔
745
                        ctx.event_loop, DefaultStateHandler {poster}));
29✔
746
}
29✔
747

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

753
        poster.PostEvent(StateEvent::Success);
22✔
754
}
22✔
755

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

759
        // Explicitly check if state scripts version is supported
760
        auto err = script_executor::CheckScriptsCompatibility(
761
                ctx.mender_context.GetConfig().paths.GetRootfsScriptsPath());
19✔
762
        if (err != error::NoError) {
19✔
UNCOV
763
                log::Error("Failed script compatibility check: " + err.String());
×
UNCOV
764
                poster.PostEvent(StateEvent::Failure);
×
765
                return;
766
        }
767

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

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

788
        poster.PostEvent(StateEvent::Success);
19✔
789
}
790

791
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
45✔
792
        DefaultAsyncErrorHandler(
45✔
793
                poster,
794
                ctx.deployment.update_module->AsyncSupportsRollback(
45✔
795
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
89✔
796
                                if (!rollback_supported.has_value()) {
45✔
797
                                        log::Error(rollback_supported.error().String());
1✔
798
                                        poster.PostEvent(StateEvent::Failure);
1✔
799
                                        return;
1✔
800
                                }
801

802
                                ctx.deployment.state_data->update_info.supports_rollback =
803
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
804
                                if (*rollback_supported) {
44✔
805
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
806
                                        poster.PostEvent(StateEvent::Success);
38✔
807
                                } else {
808
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
809
                                }
810
                        }));
45✔
811
}
45✔
812

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

816
        DefaultAsyncErrorHandler(
41✔
817
                poster,
818
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
819
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
820
}
41✔
821

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

825
        auto exp_reboot_mode =
826
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
57✔
827
        // Should always be true because we check it at load time.
828
        assert(exp_reboot_mode);
829

830
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
831
        // succeeds.
832
        auto handler = [&poster](error::Error err) {
114✔
833
                if (err != error::NoError) {
57✔
834
                        log::Error(err.String());
2✔
835
                }
836
                poster.PostEvent(StateEvent::Success);
57✔
837
        };
57✔
838

839
        error::Error err;
57✔
840
        switch (exp_reboot_mode.value()) {
57✔
841
        case update_module::RebootAction::No:
842
                // Should not happen because then we don't enter this state.
843
                assert(false);
844

UNCOV
845
                err = error::MakeError(
×
UNCOV
846
                        error::ProgrammingError, "Entered UpdateRollbackRebootState with RebootAction = No");
×
UNCOV
847
                break;
×
848

849
        case update_module::RebootAction::Yes:
57✔
850
                err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(ctx.event_loop, handler);
114✔
851
                break;
57✔
852

UNCOV
853
        case update_module::RebootAction::Automatic:
×
UNCOV
854
                err = ctx.deployment.update_module->AsyncSystemReboot(ctx.event_loop, handler);
×
UNCOV
855
                break;
×
856
        }
857

858
        if (err != error::NoError) {
57✔
UNCOV
859
                log::Error(err.String());
×
UNCOV
860
                poster.PostEvent(StateEvent::Success);
×
861
        }
862
}
57✔
863

864
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
865
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
866
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
867

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

885
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
886
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
887
        poster.PostEvent(StateEvent::Success);
50✔
888
}
50✔
889

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

893
        DefaultAsyncErrorHandler(
55✔
894
                poster,
895
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
896
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
897
}
55✔
898

899
static string AddInconsistentSuffix(const string &str) {
21✔
900
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
901
        // `string::ends_with` is C++20... grumble
902
        string ret {str};
21✔
903
        if (!common::EndsWith(ret, suffix)) {
21✔
904
                ret.append(suffix);
21✔
905
        }
906
        return ret;
21✔
907
}
908

909
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
910
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
911
                // If the update failed, but we rolled back successfully, then we don't need to do
912
                // anything, just keep the old data.
913
                poster.PostEvent(StateEvent::Success);
38✔
914
                return;
38✔
915
        }
916

917
        assert(ctx.deployment.state_data);
918
        // This state should never happen: rollback failed, but update not failed??
919
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
920

921
        // We expect Cleanup to be the next state after this.
922
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
37✔
923

924
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
925

926
        string artifact_name;
927
        if (ctx.deployment.rollback_failed) {
37✔
928
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
38✔
929
        } else {
930
                artifact_name = artifact.artifact_name;
18✔
931
        }
932

933
        bool deploy_failed = ctx.deployment.failed;
37✔
934

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

960
        poster.PostEvent(StateEvent::Success);
37✔
961
}
962

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

966
        // It's possible for there not to be an initialized update_module structure, if the
967
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
968
        // no-op.
969
        if (!ctx.deployment.update_module) {
91✔
970
                poster.PostEvent(StateEvent::Success);
9✔
971
                return;
9✔
972
        }
973

974
        DefaultAsyncErrorHandler(
82✔
975
                poster,
976
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
164✔
977
}
978

979
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
980
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
93✔
981
                // Remove state data, since we're done now.
982
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
91✔
983
                if (err != error::NoError) {
91✔
UNCOV
984
                        return err;
×
985
                }
986
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
91✔
987
        });
93✔
988
        if (err != error::NoError) {
93✔
989
                log::Error("Error removing artifact data: " + err.String());
4✔
990
                poster.PostEvent(StateEvent::Failure);
2✔
991
                return;
992
        }
993

994
        poster.PostEvent(StateEvent::Success);
91✔
995
}
996

997
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
998
        assert(ctx.deployment.state_data);
999
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
1000

1001
        // Mark update as inconsistent.
1002
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
1003

1004
        auto err = ctx.mender_context.CommitArtifactData(
2✔
1005
                artifact_name,
1006
                artifact.artifact_group,
2✔
1007
                artifact.type_info_provides,
2✔
1008
                artifact.clears_artifact_provides,
2✔
1009
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
1010
        if (err != error::NoError) {
2✔
UNCOV
1011
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
UNCOV
1012
                poster.PostEvent(StateEvent::Failure);
×
1013
                return;
1014
        }
1015

1016
        poster.PostEvent(StateEvent::Success);
2✔
1017
}
1018

1019
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
1020
        log::Info(
93✔
1021
                "Deployment with ID " + ctx.deployment.state_data->update_info.id
186✔
1022
                + " finished with status: " + string(ctx.deployment.failed ? "Failure" : "Success"));
390✔
1023

1024
        ctx.FinishDeploymentLogging();
93✔
1025

1026
        ctx.deployment = {};
93✔
1027
        poster.PostEvent(
93✔
1028
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
93✔
1029
        poster.PostEvent(StateEvent::DeploymentEnded);
93✔
1030
        poster.PostEvent(StateEvent::Success);
93✔
1031
}
93✔
1032

1033
ExitState::ExitState(events::EventLoop &event_loop) :
96✔
1034
        event_loop_(event_loop) {
192✔
1035
}
96✔
1036

1037
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
93✔
1038
#ifndef NDEBUG
1039
        if (--iterations_left_ <= 0) {
1040
                event_loop_.Stop();
1041
        } else {
1042
                poster.PostEvent(StateEvent::Success);
1043
        }
1044
#else
1045
        event_loop_.Stop();
93✔
1046
#endif
1047
}
93✔
1048

1049
namespace deployment_tracking {
1050

1051
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
63✔
1052
        ctx.deployment.failed = false;
63✔
1053
        ctx.deployment.rollback_failed = false;
63✔
1054
}
63✔
1055

1056
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
60✔
1057
        ctx.deployment.failed = true;
60✔
1058
        ctx.deployment.rollback_failed = true;
60✔
1059
}
60✔
1060

1061
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
1062
        ctx.deployment.failed = true;
52✔
1063
        ctx.deployment.rollback_failed = false;
52✔
1064
}
52✔
1065

1066
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
1067
        ctx.deployment.failed = true;
12✔
1068
        ctx.deployment.rollback_failed = true;
12✔
1069
}
12✔
1070

1071
} // namespace deployment_tracking
1072

1073
} // namespace daemon
1074
} // namespace update
1075
} // 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