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

mendersoftware / mender / 969433301

16 Aug 2023 11:23AM UTC coverage: 79.041% (+0.01%) from 79.029%
969433301

push

gitlab-ci

oleorhagen
refac(conf): Store all configuration in MenderConfig

This moves all the default configuration variables into the `MenderConfig`
class, in which it is available under the `default_config` variable.

The config struct is then passed around the code, as opposed to having globally
accessible variables, like we had last time around.

Ticket: MEN-6673
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

27 of 27 new or added lines in 9 files covered. (100.0%)

5325 of 6737 relevant lines covered (79.04%)

200.26 hits per line

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

81.44
/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 <common/conf.hpp>
18
#include <common/events_io.hpp>
19
#include <common/log.hpp>
20

21
#include <mender-update/daemon/context.hpp>
22

23
namespace mender {
24
namespace update {
25
namespace daemon {
26

27
namespace conf = mender::common::conf;
28
namespace error = mender::common::error;
29
namespace events = mender::common::events;
30
namespace log = mender::common::log;
31
namespace kv_db = mender::common::key_value_database;
32

33
namespace main_context = mender::update::context;
34

35
class DefaultStateHandler {
36
public:
37
        void operator()(const error::Error &err) {
142✔
38
                if (err != error::NoError) {
142✔
39
                        log::Error(err.String());
17✔
40
                        poster.PostEvent(StateEvent::Failure);
17✔
41
                        return;
17✔
42
                }
43
                poster.PostEvent(StateEvent::Success);
125✔
44
        }
45

46
        sm::EventPoster<StateEvent> &poster;
47
};
48

49
static void DefaultAsyncErrorHandler(sm::EventPoster<StateEvent> &poster, const error::Error &err) {
201✔
50
        if (err != error::NoError) {
201✔
51
                log::Error(err.String());
×
52
                poster.PostEvent(StateEvent::Failure);
×
53
        }
54
}
201✔
55

56
void EmptyState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
57
        // Keep this state truly empty.
58
}
73✔
59

60
void IdleState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
59✔
61
        log::Debug("Entering Idle state");
59✔
62
}
59✔
63

64
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
65
        log::Debug("Submitting inventory");
29✔
66

67
        // Schedule timer for next update first, so that long running submissions do not postpone
68
        // the schedule.
69
        poll_timer_.AsyncWait(
29✔
70
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
29✔
71
                [&poster](error::Error err) {
×
72
                        if (err != error::NoError) {
×
73
                                log::Error("Inventory poll timer caused error: " + err.String());
×
74
                        } else {
75
                                poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
76
                        }
77
                });
58✔
78

79
        // TODO: MEN-6576
80
        poster.PostEvent(StateEvent::Success);
29✔
81
}
29✔
82

83
void PollForDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
84
        log::Debug("Polling for update");
29✔
85

86
        // Schedule timer for next update first, so that long running submissions do not postpone
87
        // the schedule.
88
        poll_timer_.AsyncWait(
29✔
89
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
29✔
90
                [&poster](error::Error err) {
×
91
                        if (err != error::NoError) {
×
92
                                log::Error("Update poll timer caused error: " + err.String());
×
93
                        } else {
94
                                poster.PostEvent(StateEvent::DeploymentPollingTriggered);
×
95
                        }
96
                });
58✔
97

98
        auto err = ctx.deployment_client->CheckNewDeployments(
29✔
99
                ctx.mender_context,
100
                ctx.mender_context.GetConfig().server_url,
58✔
101
                ctx.http_client,
102
                [&ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
140✔
103
                        if (!response) {
28✔
104
                                log::Error("Error while polling for deployment: " + response.error().String());
×
105
                                poster.PostEvent(StateEvent::Failure);
×
106
                                return;
×
107
                        } else if (!response.value()) {
28✔
108
                                log::Info("No update available");
×
109
                                poster.PostEvent(StateEvent::NothingToDo);
×
110
                                return;
×
111
                        }
112

113
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
28✔
114
                        if (!exp_data) {
28✔
115
                                log::Error("Error in API response: " + exp_data.error().String());
×
116
                                poster.PostEvent(StateEvent::Failure);
×
117
                                return;
×
118
                        }
119

120
                        // Make a new set of update data.
121
                        ctx.deployment.state_data.reset(new StateData(std::move(exp_data.value())));
28✔
122

123
                        log::Info(
28✔
124
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
56✔
125

126
                        poster.PostEvent(StateEvent::DeploymentStarted);
28✔
127
                        poster.PostEvent(StateEvent::Success);
28✔
128
                });
58✔
129

130
        if (err != error::NoError) {
29✔
131
                log::Error("Error when trying to poll for deployment: " + err.String());
1✔
132
                poster.PostEvent(StateEvent::Failure);
1✔
133
        }
134
}
29✔
135

136
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
213✔
137
        assert(ctx.deployment.state_data);
213✔
138

139
        ctx.deployment.state_data->state = DatabaseStateString();
213✔
140

141
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
213✔
142
        if (err != error::NoError) {
213✔
143
                log::Error(err.String());
8✔
144
                if (err.code
8✔
145
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
8✔
146
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
147
                        return;
1✔
148
                } else if (!IsFailureState()) {
7✔
149
                        // Non-failure states should be interrupted, but failure states should be
150
                        // allowed to do their work, even if a database error was detected.
151
                        poster.PostEvent(StateEvent::Failure);
1✔
152
                        return;
1✔
153
                }
154
        }
155

156
        OnEnterSaveState(ctx, poster);
211✔
157
}
158

159
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
28✔
160
        log::Debug("Entering Download state");
28✔
161

162
        auto req = make_shared<http::OutgoingRequest>();
28✔
163
        req->SetMethod(http::Method::GET);
28✔
164
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
28✔
165
        if (err != error::NoError) {
28✔
166
                log::Error(err.String());
×
167
                poster.PostEvent(StateEvent::Failure);
×
168
                return;
×
169
        }
170

171
        err = ctx.download_client.AsyncCall(
28✔
172
                req,
173
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
111✔
174
                        if (!exp_resp) {
28✔
175
                                log::Error(exp_resp.error().String());
×
176
                                poster.PostEvent(StateEvent::Failure);
×
177
                                return;
1✔
178
                        }
179

180
                        auto &resp = exp_resp.value();
28✔
181
                        if (resp->GetStatusCode() != http::StatusOK) {
28✔
182
                                log::Error(
1✔
183
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
184
                                ctx.download_client.Cancel();
1✔
185
                                poster.PostEvent(StateEvent::Failure);
1✔
186
                                return;
1✔
187
                        }
188

189
                        auto http_reader = resp->MakeBodyAsyncReader();
54✔
190
                        ctx.deployment.artifact_reader =
191
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader);
27✔
192
                        ParseArtifact(ctx, poster);
27✔
193
                },
194
                [](http::ExpectedIncomingResponsePtr exp_resp) {
27✔
195
                        if (!exp_resp) {
27✔
196
                                log::Error(exp_resp.error().String());
×
197
                                // Cannot handle error here, because this handler is called at the
198
                                // end of the download, when we have already left this state. So
199
                                // rely on this error being propagated through the BodyAsyncReader
200
                                // above instead.
201
                                return;
×
202
                        }
203
                });
56✔
204

205
        if (err != error::NoError) {
28✔
206
                log::Error(err.String());
×
207
                poster.PostEvent(StateEvent::Failure);
×
208
                return;
×
209
        }
210
}
211

212
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
27✔
213
        artifact::config::ParserConfig config {
27✔
214
                .artifact_scripts_filesystem_path =
215
                        ctx.mender_context.GetConfig().paths.GetArtScriptsPath(),
27✔
216
                .artifact_scripts_version = 3,
217
        };
27✔
218
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
27✔
219
        if (!exp_parser) {
27✔
220
                log::Error(exp_parser.error().String());
×
221
                poster.PostEvent(StateEvent::Failure);
×
222
                return;
×
223
        }
224
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
27✔
225

226
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
27✔
227
        if (!exp_header) {
27✔
228
                log::Error(exp_header.error().String());
×
229
                poster.PostEvent(StateEvent::Failure);
×
230
                return;
×
231
        }
232
        auto &header = exp_header.value();
27✔
233

234
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
27✔
235
        if (!exp_matches) {
27✔
236
                log::Error(exp_matches.error().String());
×
237
                poster.PostEvent(StateEvent::Failure);
×
238
                return;
×
239
        } else if (!exp_matches.value()) {
27✔
240
                // reasons already logged
241
                poster.PostEvent(StateEvent::Failure);
1✔
242
                return;
1✔
243
        }
244

245
        log::Info("Installing artifact...");
26✔
246

247
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
26✔
248

249
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
26✔
250

251
        assert(ctx.deployment.state_data->update_info.artifact.payload_types.size() == 1);
26✔
252

253
        // Initial state data save, now that we have enough information from the artifact.
254
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
26✔
255
        if (err != error::NoError) {
26✔
256
                log::Error(err.String());
1✔
257
                if (err.code
1✔
258
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
1✔
259
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
260
                        return;
×
261
                } else {
262
                        poster.PostEvent(StateEvent::Failure);
1✔
263
                        return;
1✔
264
                }
265
        }
266

267
        if (header.header.payload_type == "") {
25✔
268
                // Empty-payload-artifact, aka "bootstrap artifact".
269
                poster.PostEvent(StateEvent::NothingToDo);
1✔
270
                return;
1✔
271
        }
272

273
        ctx.deployment.update_module.reset(
24✔
274
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
24✔
275

276
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
24✔
277
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
48✔
278
        if (err != error::NoError) {
24✔
279
                log::Error(err.String());
×
280
                poster.PostEvent(StateEvent::Failure);
×
281
                return;
×
282
        }
283

284
        auto exp_payload = ctx.deployment.artifact_parser->Next();
24✔
285
        if (!exp_payload) {
24✔
286
                log::Error(exp_payload.error().String());
×
287
                poster.PostEvent(StateEvent::Failure);
×
288
                return;
×
289
        }
290
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
24✔
291

292
        ctx.deployment.update_module->AsyncDownload(
48✔
293
                ctx.event_loop, *ctx.deployment.artifact_payload, [&poster](error::Error err) {
24✔
294
                        if (err != error::NoError) {
24✔
295
                                log::Error(err.String());
2✔
296
                                poster.PostEvent(StateEvent::Failure);
2✔
297
                                return;
2✔
298
                        }
299

300
                        poster.PostEvent(StateEvent::Success);
22✔
301
                });
48✔
302
}
303

304
SendStatusUpdateState::SendStatusUpdateState(
×
305
        optional::optional<deployments::DeploymentStatus> status) :
135✔
306
        status_(status),
307
        mode_(FailureMode::Ignore) {
×
308
}
×
309

310
SendStatusUpdateState::SendStatusUpdateState(
×
311
        optional::optional<deployments::DeploymentStatus> status,
312
        events::EventLoop &event_loop,
313
        int retry_interval_seconds) :
90✔
314
        status_(status),
315
        mode_(FailureMode::RetryThenFail),
316
        // MEN-2676: Cap at 10 retries.
317
        retry_(
318
                Retry {http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), 10), event_loop}) {
×
319
}
×
320

321
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
88✔
322
        if (retry_) {
88✔
323
                retry_->backoff.SetSmallestInterval(interval);
88✔
324
        }
325
}
88✔
326

327
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
117✔
328
        // Reset this every time we enter the state, which means a new round of retries.
329
        if (retry_) {
117✔
330
                retry_->backoff.Reset();
54✔
331
        }
332

333
        DoStatusUpdate(ctx, poster);
117✔
334
}
117✔
335

336
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
136✔
337
        assert(ctx.deployment_client);
136✔
338
        assert(ctx.deployment.state_data);
136✔
339

340
        log::Info("Sending status update to server");
136✔
341

342
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
335✔
343
                if (err != error::NoError) {
136✔
344
                        log::Error("Could not send deployment status: " + err.String());
24✔
345

346
                        switch (mode_) {
24✔
347
                        case FailureMode::Ignore:
3✔
348
                                break;
3✔
349
                        case FailureMode::RetryThenFail:
21✔
350
                                if (err.code
21✔
351
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
352
                                        // If the deployment was aborted upstream it is an immediate
353
                                        // failure, even if retry is enabled.
354
                                        poster.PostEvent(StateEvent::Failure);
1✔
355
                                        return;
21✔
356
                                }
357

358
                                auto exp_interval = retry_->backoff.NextInterval();
40✔
359
                                if (!exp_interval) {
20✔
360
                                        log::Error(
1✔
361
                                                "Giving up on sending status updates to server: "
362
                                                + exp_interval.error().String());
2✔
363
                                        poster.PostEvent(StateEvent::Failure);
1✔
364
                                        return;
1✔
365
                                }
366

367
                                log::Info(
19✔
368
                                        "Retrying status update after "
369
                                        + to_string(chrono::milliseconds(*exp_interval).count() / 1000) + " seconds");
38✔
370

371
                                retry_->wait_timer.AsyncWait(
19✔
372
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
19✔
373
                                                // Error here is quite unexpected (from a timer), so treat
374
                                                // this as an immediate error, despite Retry flag.
375
                                                if (err != error::NoError) {
19✔
376
                                                        log::Error(
×
377
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
378
                                                                + err.String());
×
379
                                                        poster.PostEvent(StateEvent::Failure);
×
380
                                                        return;
×
381
                                                }
382

383
                                                // Try again.
384
                                                DoStatusUpdate(ctx, poster);
19✔
385
                                        });
38✔
386
                                return;
19✔
387
                        }
388
                }
389

390
                poster.PostEvent(StateEvent::Success);
115✔
391
        };
136✔
392

393
        deployments::DeploymentStatus status;
394
        if (status_) {
136✔
395
                status = status_.value();
92✔
396
        } else {
397
                // If nothing is specified, grab success/failure status from the deployment status.
398
                if (ctx.deployment.failed) {
44✔
399
                        status = deployments::DeploymentStatus::Failure;
37✔
400
                } else {
401
                        status = deployments::DeploymentStatus::Success;
7✔
402
                }
403
        }
404

405
        auto err = ctx.deployment_client->PushStatus(
136✔
406
                ctx.deployment.state_data->update_info.id,
136✔
407
                status,
408
                "",
409
                ctx.mender_context.GetConfig().server_url,
272✔
410
                ctx.http_client,
411
                result_handler);
544✔
412

413
        if (err != error::NoError) {
136✔
414
                result_handler(err);
×
415
        }
416

417
        // No action, wait for reply from status endpoint.
418
}
136✔
419

420
void UpdateInstallState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
21✔
421
        log::Debug("Entering ArtifactInstall state");
21✔
422

423
        DefaultAsyncErrorHandler(
21✔
424
                poster,
425
                ctx.deployment.update_module->AsyncArtifactInstall(
21✔
426
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
427
}
21✔
428

429
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
36✔
430
        DefaultAsyncErrorHandler(
36✔
431
                poster,
432
                ctx.deployment.update_module->AsyncNeedsReboot(
36✔
433
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
140✔
434
                                if (!reboot_action.has_value()) {
36✔
435
                                        log::Error(reboot_action.error().String());
2✔
436
                                        poster.PostEvent(StateEvent::Failure);
2✔
437
                                        return;
2✔
438
                                }
439

440
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
34✔
441
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
34✔
442
                                        NeedsRebootToDbString(*reboot_action);
68✔
443
                                switch (*reboot_action) {
34✔
444
                                case update_module::RebootAction::No:
4✔
445
                                        poster.PostEvent(StateEvent::NothingToDo);
4✔
446
                                        break;
4✔
447
                                case update_module::RebootAction::Yes:
30✔
448
                                case update_module::RebootAction::Automatic:
449
                                        poster.PostEvent(StateEvent::Success);
30✔
450
                                        break;
30✔
451
                                }
452
                        }));
72✔
453
}
36✔
454

455
void UpdateRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
13✔
456
        log::Debug("Entering ArtifactReboot state");
13✔
457

458
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
13✔
459
        auto exp_reboot_mode =
460
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
26✔
461
        // Should always be true because we check it at load time.
462
        assert(exp_reboot_mode);
13✔
463

464
        switch (exp_reboot_mode.value()) {
13✔
465
        case update_module::RebootAction::No:
×
466
                // Should not happen because then we don't enter this state.
467
                assert(false);
×
468
                poster.PostEvent(StateEvent::Failure);
469
                break;
470
        case update_module::RebootAction::Yes:
13✔
471
                DefaultAsyncErrorHandler(
13✔
472
                        poster,
473
                        ctx.deployment.update_module->AsyncArtifactReboot(
13✔
474
                                ctx.event_loop, DefaultStateHandler {poster}));
26✔
475
                break;
13✔
476
        case update_module::RebootAction::Automatic:
×
477
                DefaultAsyncErrorHandler(
×
478
                        poster,
479
                        ctx.deployment.update_module->AsyncSystemReboot(
×
480
                                ctx.event_loop, DefaultStateHandler {poster}));
×
481
                break;
×
482
        }
483
}
13✔
484

485
void UpdateVerifyRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
14✔
486
        log::Debug("Entering ArtifactVerifyReboot state");
14✔
487

488
        DefaultAsyncErrorHandler(
14✔
489
                poster,
490
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
14✔
491
                        ctx.event_loop, DefaultStateHandler {poster}));
28✔
492
}
14✔
493

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

499
        poster.PostEvent(StateEvent::Success);
10✔
500
}
10✔
501

502
void UpdateCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
8✔
503
        log::Debug("Entering ArtifactCommit state");
8✔
504

505
        DefaultAsyncErrorHandler(
8✔
506
                poster,
507
                ctx.deployment.update_module->AsyncArtifactCommit(
8✔
508
                        ctx.event_loop, DefaultStateHandler {poster}));
16✔
509
}
8✔
510

511
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
512
        // TODO: Will need to run ArtifactCommit_Leave scripts in here. Maybe it should be renamed
513
        // to something with state scripts also.
514

515
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
516
        assert(ctx.deployment.state_data);
6✔
517
        auto &state_data = *ctx.deployment.state_data;
6✔
518
        if (state_data.update_info.has_db_schema_update) {
6✔
519
                state_data.update_info.has_db_schema_update = false;
1✔
520
                auto err = ctx.SaveDeploymentStateData(state_data);
1✔
521
                if (err != error::NoError) {
1✔
522
                        log::Error("Not able to commit schema update: " + err.String());
×
523
                        poster.PostEvent(StateEvent::Failure);
×
524
                        return;
×
525
                }
526
        }
527

528
        poster.PostEvent(StateEvent::Success);
6✔
529
}
530

531
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
23✔
532
        DefaultAsyncErrorHandler(
23✔
533
                poster,
534
                ctx.deployment.update_module->AsyncSupportsRollback(
23✔
535
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
88✔
536
                                if (!rollback_supported.has_value()) {
23✔
537
                                        log::Error(rollback_supported.error().String());
1✔
538
                                        poster.PostEvent(StateEvent::Failure);
1✔
539
                                        return;
1✔
540
                                }
541

542
                                ctx.deployment.state_data->update_info.supports_rollback =
22✔
543
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
544
                                if (*rollback_supported) {
22✔
545
                                        poster.PostEvent(StateEvent::RollbackStarted);
20✔
546
                                        poster.PostEvent(StateEvent::Success);
20✔
547
                                } else {
548
                                        poster.PostEvent(StateEvent::NothingToDo);
2✔
549
                                }
550
                        }));
46✔
551
}
23✔
552

553
void UpdateRollbackState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
21✔
554
        log::Debug("Entering ArtifactRollback state");
21✔
555

556
        DefaultAsyncErrorHandler(
21✔
557
                poster,
558
                ctx.deployment.update_module->AsyncArtifactRollback(
21✔
559
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
560
}
21✔
561

562
void UpdateRollbackRebootState::OnEnterSaveState(
29✔
563
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
564
        log::Debug("Entering ArtifactRollbackReboot state");
29✔
565

566
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
567
        // succeeds.
568
        auto err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(
569
                ctx.event_loop, [&poster](error::Error err) {
58✔
570
                        if (err != error::NoError) {
29✔
571
                                log::Error(err.String());
1✔
572
                        }
573
                        poster.PostEvent(StateEvent::Success);
29✔
574
                });
87✔
575

576
        if (err != error::NoError) {
29✔
577
                log::Error(err.String());
×
578
                poster.PostEvent(StateEvent::Success);
×
579
        }
580
}
29✔
581

582
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
30✔
583
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
584
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
30✔
585

586
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
587
        // state loop detection will eventually kick in.
588
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
589
                ctx.event_loop, [&poster](error::Error err) {
60✔
590
                        if (err != error::NoError) {
30✔
591
                                log::Error(err.String());
12✔
592
                                poster.PostEvent(StateEvent::Retry);
12✔
593
                                return;
12✔
594
                        }
595
                        poster.PostEvent(StateEvent::Success);
18✔
596
                });
60✔
597
        if (err != error::NoError) {
30✔
598
                log::Error(err.String());
×
599
                poster.PostEvent(StateEvent::Retry);
×
600
        }
601
}
30✔
602

603
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
25✔
604
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
25✔
605
        poster.PostEvent(StateEvent::Success);
25✔
606
}
25✔
607

608
void UpdateFailureState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
27✔
609
        log::Debug("Entering ArtifactFailure state");
27✔
610

611
        DefaultAsyncErrorHandler(
27✔
612
                poster,
613
                ctx.deployment.update_module->AsyncArtifactFailure(
27✔
614
                        ctx.event_loop, DefaultStateHandler {poster}));
54✔
615
}
27✔
616

617
static string AddInconsistentSuffix(const string &str) {
8✔
618
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
8✔
619
        // `string::ends_with` is C++20... grumble
620
        string ret {str};
8✔
621
        if (!common::EndsWith(ret, suffix)) {
8✔
622
                ret.append(suffix);
8✔
623
        }
624
        return ret;
8✔
625
}
626

627
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
34✔
628
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
34✔
629
                // If the update failed, but we rolled back successfully, then we don't need to do
630
                // anything, just keep the old data.
631
                poster.PostEvent(StateEvent::Success);
21✔
632
                return;
21✔
633
        }
634

635
        assert(ctx.deployment.state_data);
13✔
636
        // This state should never happen: rollback failed, but update not failed??
637
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
13✔
638

639
        // We expect Cleanup to be the next state after this.
640
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
13✔
641

642
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
13✔
643

644
        string artifact_name;
13✔
645
        if (ctx.deployment.rollback_failed) {
13✔
646
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
6✔
647
        } else {
648
                artifact_name = artifact.artifact_name;
7✔
649
        }
650

651
        auto err = ctx.mender_context.CommitArtifactData(
13✔
652
                artifact_name,
653
                artifact.artifact_group,
13✔
654
                artifact.type_info_provides,
13✔
655
                artifact.clears_artifact_provides,
13✔
656
                [&ctx](kv_db::Transaction &txn) {
13✔
657
                        // Save the Cleanup state together with the artifact data, atomically.
658
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
13✔
659
                });
26✔
660
        if (err != error::NoError) {
13✔
661
                log::Error("Error saving artifact data: " + err.String());
×
662
                if (err.code
×
663
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
664
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
665
                        return;
×
666
                }
667
                poster.PostEvent(StateEvent::Failure);
×
668
                return;
×
669
        }
670

671
        poster.PostEvent(StateEvent::Success);
13✔
672
}
673

674
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
42✔
675
        log::Debug("Entering ArtifactCleanup state");
42✔
676

677
        // It's possible for there not to be an initialized update_module structure, if the
678
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
679
        // no-op.
680
        if (!ctx.deployment.update_module) {
42✔
681
                poster.PostEvent(StateEvent::Success);
4✔
682
                return;
4✔
683
        }
684

685
        DefaultAsyncErrorHandler(
38✔
686
                poster,
687
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
76✔
688
}
689

690
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
44✔
691
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
44✔
692
                // Remove state data, since we're done now.
693
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
84✔
694
                if (err != error::NoError) {
42✔
695
                        return err;
×
696
                }
697
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
42✔
698
        });
44✔
699
        if (err != error::NoError) {
44✔
700
                log::Error("Error removing artifact data: " + err.String());
2✔
701
                poster.PostEvent(StateEvent::Failure);
2✔
702
                return;
2✔
703
        }
704

705
        poster.PostEvent(StateEvent::Success);
42✔
706
}
707

708
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
709
        assert(ctx.deployment.state_data);
2✔
710
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
2✔
711

712
        // Mark update as inconsistent.
713
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
714

715
        auto err = ctx.mender_context.CommitArtifactData(
2✔
716
                artifact_name,
717
                artifact.artifact_group,
2✔
718
                artifact.type_info_provides,
2✔
719
                artifact.clears_artifact_provides,
2✔
720
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
721
        if (err != error::NoError) {
2✔
722
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
723
                poster.PostEvent(StateEvent::Failure);
×
724
                return;
×
725
        }
726

727
        poster.PostEvent(StateEvent::Success);
2✔
728
}
729

730
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
44✔
731
        ctx.deployment = {};
44✔
732
        poster.PostEvent(StateEvent::DeploymentEnded);
44✔
733
        poster.PostEvent(StateEvent::Success);
44✔
734
}
44✔
735

736
ExitState::ExitState(events::EventLoop &event_loop) :
×
737
        event_loop_(event_loop) {
×
738
}
×
739

740
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
44✔
741
        event_loop_.Stop();
44✔
742
}
44✔
743

744
namespace deployment_tracking {
745

746
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
30✔
747
        ctx.deployment.failed = false;
30✔
748
        ctx.deployment.rollback_failed = false;
30✔
749
}
30✔
750

751
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
30✔
752
        ctx.deployment.failed = true;
30✔
753
        ctx.deployment.rollback_failed = true;
30✔
754
}
30✔
755

756
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
26✔
757
        ctx.deployment.failed = true;
26✔
758
        ctx.deployment.rollback_failed = false;
26✔
759
}
26✔
760

761
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
762
        ctx.deployment.failed = true;
6✔
763
        ctx.deployment.rollback_failed = true;
6✔
764
}
6✔
765

766
} // namespace deployment_tracking
767

768
} // namespace daemon
769
} // namespace update
770
} // 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