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

mendersoftware / mender / 968408678

pending completion
968408678

push

gitlab-ci

oleorhagen
feat(artifact/scripts): Add support for executing state scripts

Ticket: MEN-6636
Changelog: None

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

126 of 126 new or added lines in 4 files covered. (100.0%)

5405 of 6857 relevant lines covered (78.82%)

195.75 hits per line

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

81.78
/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) {
71✔
57
        // Keep this state truly empty.
58
}
71✔
59

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

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

67
        // Schedule timer for next update first, so that long running submissions do not postpone
68
        // the schedule.
69
        poll_timer_.AsyncWait(
28✔
70
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
28✔
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
                });
56✔
78

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

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

86
        // Schedule timer for next update first, so that long running submissions do not postpone
87
        // the schedule.
88
        poll_timer_.AsyncWait(
28✔
89
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
28✔
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
                });
56✔
97

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

113
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
27✔
114
                        if (!exp_data) {
27✔
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())));
27✔
122

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

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

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

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

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

141
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
212✔
142
        if (err != error::NoError) {
212✔
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);
210✔
157
}
158

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

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

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

180
                        auto &resp = exp_resp.value();
27✔
181
                        if (resp->GetStatusCode() != http::StatusOK) {
27✔
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();
52✔
190
                        ctx.deployment.artifact_reader =
191
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader);
26✔
192
                        ParseArtifact(ctx, poster);
26✔
193
                },
194
                [](http::ExpectedIncomingResponsePtr exp_resp) {
26✔
195
                        if (!exp_resp) {
26✔
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
                });
54✔
204

205
        if (err != error::NoError) {
27✔
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) {
26✔
213
        artifact::config::ParserConfig config {
26✔
214
                .artifact_scripts_filesystem_path =
215
                        ctx.mender_context.GetConfig().default_config.GetDefaultArtScriptsPath(),
26✔
216
                .artifact_scripts_version = 3,
217
        };
26✔
218
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
26✔
219
        if (!exp_parser) {
26✔
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())));
26✔
225

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

234
        log::Info("Installing artifact...");
26✔
235

236
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
26✔
237

238
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
26✔
239

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

242
        // Initial state data save, now that we have enough information from the artifact.
243
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
26✔
244
        if (err != error::NoError) {
26✔
245
                log::Error(err.String());
1✔
246
                if (err.code
1✔
247
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
1✔
248
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
249
                        return;
×
250
                } else {
251
                        poster.PostEvent(StateEvent::Failure);
1✔
252
                        return;
1✔
253
                }
254
        }
255

256
        if (header.header.payload_type == "") {
25✔
257
                // Empty-payload-artifact, aka "bootstrap artifact".
258
                poster.PostEvent(StateEvent::NothingToDo);
1✔
259
                return;
1✔
260
        }
261

262
        ctx.deployment.update_module.reset(
24✔
263
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
24✔
264

265
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
24✔
266
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
48✔
267
        if (err != error::NoError) {
24✔
268
                log::Error(err.String());
×
269
                poster.PostEvent(StateEvent::Failure);
×
270
                return;
×
271
        }
272

273
        auto exp_payload = ctx.deployment.artifact_parser->Next();
24✔
274
        if (!exp_payload) {
24✔
275
                log::Error(exp_payload.error().String());
×
276
                poster.PostEvent(StateEvent::Failure);
×
277
                return;
×
278
        }
279
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
24✔
280

281
        ctx.deployment.update_module->AsyncDownload(
48✔
282
                ctx.event_loop, *ctx.deployment.artifact_payload, [&poster](error::Error err) {
24✔
283
                        if (err != error::NoError) {
24✔
284
                                log::Error(err.String());
2✔
285
                                poster.PostEvent(StateEvent::Failure);
2✔
286
                                return;
2✔
287
                        }
288

289
                        poster.PostEvent(StateEvent::Success);
22✔
290
                });
48✔
291
}
292

293
SendStatusUpdateState::SendStatusUpdateState(
×
294
        optional::optional<deployments::DeploymentStatus> status) :
132✔
295
        status_(status),
296
        mode_(FailureMode::Ignore) {
×
297
}
×
298

299
SendStatusUpdateState::SendStatusUpdateState(
×
300
        optional::optional<deployments::DeploymentStatus> status,
301
        events::EventLoop &event_loop,
302
        int retry_interval_seconds) :
88✔
303
        status_(status),
304
        mode_(FailureMode::RetryThenFail),
305
        // MEN-2676: Cap at 10 retries.
306
        retry_(
307
                Retry {http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), 10), event_loop}) {
×
308
}
×
309

310
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
86✔
311
        if (retry_) {
86✔
312
                retry_->backoff.SetSmallestInterval(interval);
86✔
313
        }
314
}
86✔
315

316
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
115✔
317
        // Reset this every time we enter the state, which means a new round of retries.
318
        if (retry_) {
115✔
319
                retry_->backoff.Reset();
53✔
320
        }
321

322
        DoStatusUpdate(ctx, poster);
115✔
323
}
115✔
324

325
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
134✔
326
        assert(ctx.deployment_client);
134✔
327
        assert(ctx.deployment.state_data);
134✔
328

329
        log::Info("Sending status update to server");
134✔
330

331
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
331✔
332
                if (err != error::NoError) {
134✔
333
                        log::Error("Could not send deployment status: " + err.String());
24✔
334

335
                        switch (mode_) {
24✔
336
                        case FailureMode::Ignore:
3✔
337
                                break;
3✔
338
                        case FailureMode::RetryThenFail:
21✔
339
                                if (err.code
21✔
340
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
341
                                        // If the deployment was aborted upstream it is an immediate
342
                                        // failure, even if retry is enabled.
343
                                        poster.PostEvent(StateEvent::Failure);
1✔
344
                                        return;
21✔
345
                                }
346

347
                                auto exp_interval = retry_->backoff.NextInterval();
40✔
348
                                if (!exp_interval) {
20✔
349
                                        log::Error(
1✔
350
                                                "Giving up on sending status updates to server: "
351
                                                + exp_interval.error().String());
2✔
352
                                        poster.PostEvent(StateEvent::Failure);
1✔
353
                                        return;
1✔
354
                                }
355

356
                                log::Info(
19✔
357
                                        "Retrying status update after "
358
                                        + to_string(chrono::milliseconds(*exp_interval).count() / 1000) + " seconds");
38✔
359

360
                                retry_->wait_timer.AsyncWait(
19✔
361
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
19✔
362
                                                // Error here is quite unexpected (from a timer), so treat
363
                                                // this as an immediate error, despite Retry flag.
364
                                                if (err != error::NoError) {
19✔
365
                                                        log::Error(
×
366
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
367
                                                                + err.String());
×
368
                                                        poster.PostEvent(StateEvent::Failure);
×
369
                                                        return;
×
370
                                                }
371

372
                                                // Try again.
373
                                                DoStatusUpdate(ctx, poster);
19✔
374
                                        });
38✔
375
                                return;
19✔
376
                        }
377
                }
378

379
                poster.PostEvent(StateEvent::Success);
113✔
380
        };
134✔
381

382
        deployments::DeploymentStatus status;
383
        if (status_) {
134✔
384
                status = status_.value();
91✔
385
        } else {
386
                // If nothing is specified, grab success/failure status from the deployment status.
387
                if (ctx.deployment.failed) {
43✔
388
                        status = deployments::DeploymentStatus::Failure;
36✔
389
                } else {
390
                        status = deployments::DeploymentStatus::Success;
7✔
391
                }
392
        }
393

394
        auto err = ctx.deployment_client->PushStatus(
134✔
395
                ctx.deployment.state_data->update_info.id,
134✔
396
                status,
397
                "",
398
                ctx.mender_context.GetConfig().server_url,
268✔
399
                ctx.http_client,
400
                result_handler);
536✔
401

402
        if (err != error::NoError) {
134✔
403
                result_handler(err);
×
404
        }
405

406
        // No action, wait for reply from status endpoint.
407
}
134✔
408

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

412
        DefaultAsyncErrorHandler(
21✔
413
                poster,
414
                ctx.deployment.update_module->AsyncArtifactInstall(
21✔
415
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
416
}
21✔
417

418
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
36✔
419
        DefaultAsyncErrorHandler(
36✔
420
                poster,
421
                ctx.deployment.update_module->AsyncNeedsReboot(
36✔
422
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
140✔
423
                                if (!reboot_action.has_value()) {
36✔
424
                                        log::Error(reboot_action.error().String());
2✔
425
                                        poster.PostEvent(StateEvent::Failure);
2✔
426
                                        return;
2✔
427
                                }
428

429
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
34✔
430
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
34✔
431
                                        NeedsRebootToDbString(*reboot_action);
68✔
432
                                switch (*reboot_action) {
34✔
433
                                case update_module::RebootAction::No:
4✔
434
                                        poster.PostEvent(StateEvent::NothingToDo);
4✔
435
                                        break;
4✔
436
                                case update_module::RebootAction::Yes:
30✔
437
                                case update_module::RebootAction::Automatic:
438
                                        poster.PostEvent(StateEvent::Success);
30✔
439
                                        break;
30✔
440
                                }
441
                        }));
72✔
442
}
36✔
443

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

447
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
13✔
448
        auto exp_reboot_mode =
449
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
26✔
450
        // Should always be true because we check it at load time.
451
        assert(exp_reboot_mode);
13✔
452

453
        switch (exp_reboot_mode.value()) {
13✔
454
        case update_module::RebootAction::No:
×
455
                // Should not happen because then we don't enter this state.
456
                assert(false);
×
457
                poster.PostEvent(StateEvent::Failure);
458
                break;
459
        case update_module::RebootAction::Yes:
13✔
460
                DefaultAsyncErrorHandler(
13✔
461
                        poster,
462
                        ctx.deployment.update_module->AsyncArtifactReboot(
13✔
463
                                ctx.event_loop, DefaultStateHandler {poster}));
26✔
464
                break;
13✔
465
        case update_module::RebootAction::Automatic:
×
466
                DefaultAsyncErrorHandler(
×
467
                        poster,
468
                        ctx.deployment.update_module->AsyncSystemReboot(
×
469
                                ctx.event_loop, DefaultStateHandler {poster}));
×
470
                break;
×
471
        }
472
}
13✔
473

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

477
        DefaultAsyncErrorHandler(
14✔
478
                poster,
479
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
14✔
480
                        ctx.event_loop, DefaultStateHandler {poster}));
28✔
481
}
14✔
482

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

488
        poster.PostEvent(StateEvent::Success);
10✔
489
}
10✔
490

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

494
        DefaultAsyncErrorHandler(
8✔
495
                poster,
496
                ctx.deployment.update_module->AsyncArtifactCommit(
8✔
497
                        ctx.event_loop, DefaultStateHandler {poster}));
16✔
498
}
8✔
499

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

504
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
505
        assert(ctx.deployment.state_data);
6✔
506
        auto &state_data = *ctx.deployment.state_data;
6✔
507
        if (state_data.update_info.has_db_schema_update) {
6✔
508
                state_data.update_info.has_db_schema_update = false;
1✔
509
                auto err = ctx.SaveDeploymentStateData(state_data);
1✔
510
                if (err != error::NoError) {
1✔
511
                        log::Error("Not able to commit schema update: " + err.String());
×
512
                        poster.PostEvent(StateEvent::Failure);
×
513
                        return;
×
514
                }
515
        }
516

517
        poster.PostEvent(StateEvent::Success);
6✔
518
}
519

520
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
23✔
521
        DefaultAsyncErrorHandler(
23✔
522
                poster,
523
                ctx.deployment.update_module->AsyncSupportsRollback(
23✔
524
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
88✔
525
                                if (!rollback_supported.has_value()) {
23✔
526
                                        log::Error(rollback_supported.error().String());
1✔
527
                                        poster.PostEvent(StateEvent::Failure);
1✔
528
                                        return;
1✔
529
                                }
530

531
                                ctx.deployment.state_data->update_info.supports_rollback =
22✔
532
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
533
                                if (*rollback_supported) {
22✔
534
                                        poster.PostEvent(StateEvent::RollbackStarted);
20✔
535
                                        poster.PostEvent(StateEvent::Success);
20✔
536
                                } else {
537
                                        poster.PostEvent(StateEvent::NothingToDo);
2✔
538
                                }
539
                        }));
46✔
540
}
23✔
541

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

545
        DefaultAsyncErrorHandler(
21✔
546
                poster,
547
                ctx.deployment.update_module->AsyncArtifactRollback(
21✔
548
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
549
}
21✔
550

551
void UpdateRollbackRebootState::OnEnterSaveState(
29✔
552
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
553
        log::Debug("Entering ArtifactRollbackReboot state");
29✔
554

555
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
556
        // succeeds.
557
        auto err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(
558
                ctx.event_loop, [&poster](error::Error err) {
58✔
559
                        if (err != error::NoError) {
29✔
560
                                log::Error(err.String());
1✔
561
                        }
562
                        poster.PostEvent(StateEvent::Success);
29✔
563
                });
87✔
564

565
        if (err != error::NoError) {
29✔
566
                log::Error(err.String());
×
567
                poster.PostEvent(StateEvent::Success);
×
568
        }
569
}
29✔
570

571
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
30✔
572
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
573
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
30✔
574

575
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
576
        // state loop detection will eventually kick in.
577
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
578
                ctx.event_loop, [&poster](error::Error err) {
60✔
579
                        if (err != error::NoError) {
30✔
580
                                log::Error(err.String());
12✔
581
                                poster.PostEvent(StateEvent::Retry);
12✔
582
                                return;
12✔
583
                        }
584
                        poster.PostEvent(StateEvent::Success);
18✔
585
                });
60✔
586
        if (err != error::NoError) {
30✔
587
                log::Error(err.String());
×
588
                poster.PostEvent(StateEvent::Retry);
×
589
        }
590
}
30✔
591

592
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
24✔
593
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
24✔
594
        poster.PostEvent(StateEvent::Success);
24✔
595
}
24✔
596

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

600
        DefaultAsyncErrorHandler(
27✔
601
                poster,
602
                ctx.deployment.update_module->AsyncArtifactFailure(
27✔
603
                        ctx.event_loop, DefaultStateHandler {poster}));
54✔
604
}
27✔
605

606
static string AddInconsistentSuffix(const string &str) {
8✔
607
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
8✔
608
        // `string::ends_with` is C++20... grumble
609
        string ret {str};
8✔
610
        if (!common::EndsWith(ret, suffix)) {
8✔
611
                ret.append(suffix);
8✔
612
        }
613
        return ret;
8✔
614
}
615

616
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
34✔
617
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
34✔
618
                // If the update failed, but we rolled back successfully, then we don't need to do
619
                // anything, just keep the old data.
620
                poster.PostEvent(StateEvent::Success);
21✔
621
                return;
21✔
622
        }
623

624
        assert(ctx.deployment.state_data);
13✔
625
        // This state should never happen: rollback failed, but update not failed??
626
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
13✔
627

628
        // We expect Cleanup to be the next state after this.
629
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
13✔
630

631
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
13✔
632

633
        string artifact_name;
13✔
634
        if (ctx.deployment.rollback_failed) {
13✔
635
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
6✔
636
        } else {
637
                artifact_name = artifact.artifact_name;
7✔
638
        }
639

640
        auto err = ctx.mender_context.CommitArtifactData(
13✔
641
                artifact_name,
642
                artifact.artifact_group,
13✔
643
                artifact.type_info_provides,
13✔
644
                artifact.clears_artifact_provides,
13✔
645
                [&ctx](kv_db::Transaction &txn) {
13✔
646
                        // Save the Cleanup state together with the artifact data, atomically.
647
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
13✔
648
                });
26✔
649
        if (err != error::NoError) {
13✔
650
                log::Error("Error saving artifact data: " + err.String());
×
651
                if (err.code
×
652
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
653
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
654
                        return;
×
655
                }
656
                poster.PostEvent(StateEvent::Failure);
×
657
                return;
×
658
        }
659

660
        poster.PostEvent(StateEvent::Success);
13✔
661
}
662

663
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
41✔
664
        log::Debug("Entering ArtifactCleanup state");
41✔
665

666
        // It's possible for there not to be an initialized update_module structure, if the
667
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
668
        // no-op.
669
        if (!ctx.deployment.update_module) {
41✔
670
                poster.PostEvent(StateEvent::Success);
3✔
671
                return;
3✔
672
        }
673

674
        DefaultAsyncErrorHandler(
38✔
675
                poster,
676
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
76✔
677
}
678

679
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
43✔
680
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
43✔
681
                // Remove state data, since we're done now.
682
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
82✔
683
                if (err != error::NoError) {
41✔
684
                        return err;
×
685
                }
686
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
41✔
687
        });
43✔
688
        if (err != error::NoError) {
43✔
689
                log::Error("Error removing artifact data: " + err.String());
2✔
690
                poster.PostEvent(StateEvent::Failure);
2✔
691
                return;
2✔
692
        }
693

694
        poster.PostEvent(StateEvent::Success);
41✔
695
}
696

697
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
698
        assert(ctx.deployment.state_data);
2✔
699
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
2✔
700

701
        // Mark update as inconsistent.
702
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
703

704
        auto err = ctx.mender_context.CommitArtifactData(
2✔
705
                artifact_name,
706
                artifact.artifact_group,
2✔
707
                artifact.type_info_provides,
2✔
708
                artifact.clears_artifact_provides,
2✔
709
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
710
        if (err != error::NoError) {
2✔
711
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
712
                poster.PostEvent(StateEvent::Failure);
×
713
                return;
×
714
        }
715

716
        poster.PostEvent(StateEvent::Success);
2✔
717
}
718

719
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
43✔
720
        ctx.deployment = {};
43✔
721
        poster.PostEvent(StateEvent::DeploymentEnded);
43✔
722
        poster.PostEvent(StateEvent::Success);
43✔
723
}
43✔
724

725
ExitState::ExitState(events::EventLoop &event_loop) :
×
726
        event_loop_(event_loop) {
×
727
}
×
728

729
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
43✔
730
        event_loop_.Stop();
43✔
731
}
43✔
732

733
namespace deployment_tracking {
734

735
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
736
        ctx.deployment.failed = false;
29✔
737
        ctx.deployment.rollback_failed = false;
29✔
738
}
29✔
739

740
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
741
        ctx.deployment.failed = true;
29✔
742
        ctx.deployment.rollback_failed = true;
29✔
743
}
29✔
744

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

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

755
} // namespace deployment_tracking
756

757
} // namespace daemon
758
} // namespace update
759
} // 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