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

mendersoftware / mender / 1001210836

12 Sep 2023 01:20PM UTC coverage: 79.201% (+0.09%) from 79.112%
1001210836

push

gitlab-ci

vpodzime
feat: Add support for signals delivering two strings

Ticket: MEN-6651
Changelog: none
Signed-off-by: Vratislav Podzimek <v.podzimek@mykolab.com>

5731 of 7236 relevant lines covered (79.2%)

256.26 hits per line

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

82.45
/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
#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::common::conf;
30
namespace error = mender::common::error;
31
namespace events = mender::common::events;
32
namespace log = mender::common::log;
33
namespace kv_db = mender::common::key_value_database;
34
namespace path = mender::common::path;
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) {
142✔
42
                if (err != error::NoError) {
142✔
43
                        log::Error(err.String());
17✔
44
                        poster.PostEvent(StateEvent::Failure);
17✔
45
                        return;
17✔
46
                }
47
                poster.PostEvent(StateEvent::Success);
125✔
48
        }
49

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

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

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

64
void IdleState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
66✔
65
        log::Debug("Entering Idle state");
66✔
66
}
66✔
67

68
void SubmitInventoryState::DoSubmitInventory(Context &ctx, sm::EventPoster<StateEvent> &poster) {
33✔
69
        log::Debug("Submitting inventory");
33✔
70

71
        auto handler = [&poster](error::Error err) {
66✔
72
                if (err != error::NoError) {
33✔
73
                        log::Error("Failed to submit inventory: " + err.String());
1✔
74
                }
75
                poster.PostEvent((err == error::NoError) ? StateEvent::Success : StateEvent::Failure);
33✔
76
        };
33✔
77

78
        auto err = ctx.inventory_client->PushData(
33✔
79
                ctx.mender_context.GetConfig().paths.GetInventoryScriptsDir(),
66✔
80
                ctx.mender_context.GetConfig().server_url,
33✔
81
                ctx.event_loop,
82
                ctx.http_client,
83
                handler);
99✔
84

85
        if (err != error::NoError) {
33✔
86
                // This is the only case the handler won't be called for us by
87
                // PushData() (see inventory::PushInventoryData()).
88
                handler(err);
1✔
89
        }
90
}
33✔
91

92
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
33✔
93
        // Schedule timer for next update first, so that long running submissions do not postpone
94
        // the schedule.
95
        poll_timer_.AsyncWait(
33✔
96
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
33✔
97
                [&poster](error::Error err) {
2✔
98
                        if (err != error::NoError) {
1✔
99
                                log::Error("Inventory poll timer caused error: " + err.String());
×
100
                        } else {
101
                                poster.PostEvent(StateEvent::InventoryPollingTriggered);
1✔
102
                        }
103
                });
67✔
104

105
        DoSubmitInventory(ctx, poster);
33✔
106
}
33✔
107

108
void PollForDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
32✔
109
        log::Debug("Polling for update");
32✔
110

111
        // Schedule timer for next update first, so that long running submissions do not postpone
112
        // the schedule.
113
        poll_timer_.AsyncWait(
32✔
114
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
32✔
115
                [&poster](error::Error err) {
×
116
                        if (err != error::NoError) {
×
117
                                log::Error("Update poll timer caused error: " + err.String());
×
118
                        } else {
119
                                poster.PostEvent(StateEvent::DeploymentPollingTriggered);
×
120
                        }
121
                });
64✔
122

123
        auto err = ctx.deployment_client->CheckNewDeployments(
32✔
124
                ctx.mender_context,
125
                ctx.mender_context.GetConfig().server_url,
64✔
126
                ctx.http_client,
127
                [&ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
180✔
128
                        if (!response) {
30✔
129
                                log::Error("Error while polling for deployment: " + response.error().String());
×
130
                                poster.PostEvent(StateEvent::Failure);
×
131
                                return;
×
132
                        } else if (!response.value()) {
30✔
133
                                log::Info("No update available");
×
134
                                poster.PostEvent(StateEvent::NothingToDo);
×
135
                                return;
×
136
                        }
137

138
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
30✔
139
                        if (!exp_data) {
30✔
140
                                log::Error("Error in API response: " + exp_data.error().String());
×
141
                                poster.PostEvent(StateEvent::Failure);
×
142
                                return;
×
143
                        }
144

145
                        // Make a new set of update data.
146
                        ctx.deployment.state_data.reset(new StateData(std::move(exp_data.value())));
30✔
147

148
                        ctx.BeginDeploymentLogging();
30✔
149

150
                        log::Info("Running Mender client " + conf::kMenderVersion);
30✔
151
                        log::Info(
30✔
152
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
60✔
153

154
                        poster.PostEvent(StateEvent::DeploymentStarted);
30✔
155
                        poster.PostEvent(StateEvent::Success);
30✔
156
                });
64✔
157

158
        if (err != error::NoError) {
32✔
159
                log::Error("Error when trying to poll for deployment: " + err.String());
2✔
160
                poster.PostEvent(StateEvent::Failure);
2✔
161
        }
162
}
32✔
163

164
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
215✔
165
        assert(ctx.deployment.state_data);
215✔
166

167
        ctx.deployment.state_data->state = DatabaseStateString();
215✔
168

169
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
215✔
170
        if (err != error::NoError) {
215✔
171
                log::Error(err.String());
8✔
172
                if (err.code
8✔
173
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
8✔
174
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
175
                        return;
1✔
176
                } else if (!IsFailureState()) {
7✔
177
                        // Non-failure states should be interrupted, but failure states should be
178
                        // allowed to do their work, even if a database error was detected.
179
                        poster.PostEvent(StateEvent::Failure);
1✔
180
                        return;
1✔
181
                }
182
        }
183

184
        OnEnterSaveState(ctx, poster);
213✔
185
}
186

187
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
30✔
188
        log::Debug("Entering Download state");
30✔
189

190
        auto req = make_shared<http::OutgoingRequest>();
30✔
191
        req->SetMethod(http::Method::GET);
30✔
192
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
30✔
193
        if (err != error::NoError) {
30✔
194
                log::Error(err.String());
×
195
                poster.PostEvent(StateEvent::Failure);
×
196
                return;
×
197
        }
198

199
        err = ctx.download_client.AsyncCall(
30✔
200
                req,
201
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
119✔
202
                        if (!exp_resp) {
30✔
203
                                log::Error(exp_resp.error().String());
×
204
                                poster.PostEvent(StateEvent::Failure);
×
205
                                return;
1✔
206
                        }
207

208
                        auto &resp = exp_resp.value();
30✔
209
                        if (resp->GetStatusCode() != http::StatusOK) {
30✔
210
                                log::Error(
1✔
211
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
212
                                ctx.download_client.Cancel();
1✔
213
                                poster.PostEvent(StateEvent::Failure);
1✔
214
                                return;
1✔
215
                        }
216

217
                        auto http_reader = resp->MakeBodyAsyncReader();
29✔
218
                        if (!http_reader) {
29✔
219
                                log::Error(http_reader.error().String());
×
220
                                ctx.download_client.Cancel();
×
221
                                poster.PostEvent(StateEvent::Failure);
×
222
                                return;
×
223
                        }
224
                        ctx.deployment.artifact_reader =
225
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
29✔
226
                        ParseArtifact(ctx, poster);
29✔
227
                },
228
                [](http::ExpectedIncomingResponsePtr exp_resp) {
×
229
                        if (!exp_resp) {
×
230
                                log::Error(exp_resp.error().String());
×
231
                                // Cannot handle error here, because this handler is called at the
232
                                // end of the download, when we have already left this state. So
233
                                // rely on this error being propagated through the BodyAsyncReader
234
                                // above instead.
235
                                return;
×
236
                        }
237
                });
60✔
238

239
        if (err != error::NoError) {
30✔
240
                log::Error(err.String());
×
241
                poster.PostEvent(StateEvent::Failure);
×
242
                return;
×
243
        }
244
}
245

246
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
29✔
247
        artifact::config::ParserConfig config {
29✔
248
                .artifact_scripts_filesystem_path =
249
                        ctx.mender_context.GetConfig().paths.GetArtScriptsPath(),
29✔
250
                .artifact_scripts_version = 3,
251
        };
29✔
252
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
29✔
253
        if (!exp_parser) {
29✔
254
                log::Error(exp_parser.error().String());
×
255
                poster.PostEvent(StateEvent::Failure);
×
256
                return;
×
257
        }
258
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
29✔
259

260
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
29✔
261
        if (!exp_header) {
29✔
262
                log::Error(exp_header.error().String());
×
263
                poster.PostEvent(StateEvent::Failure);
×
264
                return;
×
265
        }
266
        auto &header = exp_header.value();
29✔
267

268
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
29✔
269
        if (!exp_matches) {
29✔
270
                log::Error(exp_matches.error().String());
2✔
271
                poster.PostEvent(StateEvent::Failure);
2✔
272
                return;
2✔
273
        } else if (!exp_matches.value()) {
27✔
274
                // reasons already logged
275
                poster.PostEvent(StateEvent::Failure);
1✔
276
                return;
1✔
277
        }
278

279
        log::Info("Installing artifact...");
26✔
280

281
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
26✔
282

283
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
26✔
284

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

287
        // Initial state data save, now that we have enough information from the artifact.
288
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
26✔
289
        if (err != error::NoError) {
26✔
290
                log::Error(err.String());
1✔
291
                if (err.code
1✔
292
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
1✔
293
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
294
                        return;
×
295
                } else {
296
                        poster.PostEvent(StateEvent::Failure);
1✔
297
                        return;
1✔
298
                }
299
        }
300

301
        if (header.header.payload_type == "") {
25✔
302
                // Empty-payload-artifact, aka "bootstrap artifact".
303
                poster.PostEvent(StateEvent::NothingToDo);
1✔
304
                return;
1✔
305
        }
306

307
        ctx.deployment.update_module.reset(
24✔
308
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
24✔
309

310
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
24✔
311
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
48✔
312
        if (err != error::NoError) {
24✔
313
                log::Error(err.String());
×
314
                poster.PostEvent(StateEvent::Failure);
×
315
                return;
×
316
        }
317

318
        auto exp_payload = ctx.deployment.artifact_parser->Next();
24✔
319
        if (!exp_payload) {
24✔
320
                log::Error(exp_payload.error().String());
×
321
                poster.PostEvent(StateEvent::Failure);
×
322
                return;
×
323
        }
324
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
24✔
325

326
        ctx.deployment.update_module->AsyncDownload(
48✔
327
                ctx.event_loop, *ctx.deployment.artifact_payload, [&poster](error::Error err) {
24✔
328
                        if (err != error::NoError) {
24✔
329
                                log::Error(err.String());
2✔
330
                                poster.PostEvent(StateEvent::Failure);
2✔
331
                                return;
2✔
332
                        }
333

334
                        poster.PostEvent(StateEvent::Success);
22✔
335
                });
48✔
336
}
337

338
SendStatusUpdateState::SendStatusUpdateState(
×
339
        optional::optional<deployments::DeploymentStatus> status) :
144✔
340
        status_(status),
341
        mode_(FailureMode::Ignore) {
×
342
}
×
343

344
SendStatusUpdateState::SendStatusUpdateState(
×
345
        optional::optional<deployments::DeploymentStatus> status,
346
        events::EventLoop &event_loop,
347
        int retry_interval_seconds) :
96✔
348
        status_(status),
349
        mode_(FailureMode::RetryThenFail),
350
        // MEN-2676: Cap at 10 retries.
351
        retry_(
352
                Retry {http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), 10), event_loop}) {
×
353
}
×
354

355
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
88✔
356
        if (retry_) {
88✔
357
                retry_->backoff.SetSmallestInterval(interval);
88✔
358
        }
359
}
88✔
360

361
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
121✔
362
        // Reset this every time we enter the state, which means a new round of retries.
363
        if (retry_) {
121✔
364
                retry_->backoff.Reset();
56✔
365
        }
366

367
        DoStatusUpdate(ctx, poster);
121✔
368
}
121✔
369

370
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
140✔
371
        assert(ctx.deployment_client);
140✔
372
        assert(ctx.deployment.state_data);
140✔
373

374
        log::Info("Sending status update to server");
140✔
375

376
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
343✔
377
                if (err != error::NoError) {
140✔
378
                        log::Error("Could not send deployment status: " + err.String());
24✔
379

380
                        switch (mode_) {
24✔
381
                        case FailureMode::Ignore:
3✔
382
                                break;
3✔
383
                        case FailureMode::RetryThenFail:
21✔
384
                                if (err.code
21✔
385
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
386
                                        // If the deployment was aborted upstream it is an immediate
387
                                        // failure, even if retry is enabled.
388
                                        poster.PostEvent(StateEvent::Failure);
1✔
389
                                        return;
21✔
390
                                }
391

392
                                auto exp_interval = retry_->backoff.NextInterval();
40✔
393
                                if (!exp_interval) {
20✔
394
                                        log::Error(
1✔
395
                                                "Giving up on sending status updates to server: "
396
                                                + exp_interval.error().String());
2✔
397
                                        poster.PostEvent(StateEvent::Failure);
1✔
398
                                        return;
1✔
399
                                }
400

401
                                log::Info(
19✔
402
                                        "Retrying status update after "
403
                                        + to_string(chrono::milliseconds(*exp_interval).count() / 1000) + " seconds");
38✔
404

405
                                retry_->wait_timer.AsyncWait(
19✔
406
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
19✔
407
                                                // Error here is quite unexpected (from a timer), so treat
408
                                                // this as an immediate error, despite Retry flag.
409
                                                if (err != error::NoError) {
19✔
410
                                                        log::Error(
×
411
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
412
                                                                + err.String());
×
413
                                                        poster.PostEvent(StateEvent::Failure);
×
414
                                                        return;
×
415
                                                }
416

417
                                                // Try again. Since both status and logs are sent
418
                                                // from here, there's a chance this might resubmit
419
                                                // the status, but there's no harm in it, and it
420
                                                // won't happen often.
421
                                                DoStatusUpdate(ctx, poster);
19✔
422
                                        });
38✔
423
                                return;
19✔
424
                        }
425
                }
426

427
                poster.PostEvent(StateEvent::Success);
119✔
428
        };
140✔
429

430
        deployments::DeploymentStatus status;
431
        if (status_) {
140✔
432
                status = status_.value();
94✔
433
        } else {
434
                // If nothing is specified, grab success/failure status from the deployment status.
435
                if (ctx.deployment.failed) {
46✔
436
                        status = deployments::DeploymentStatus::Failure;
39✔
437
                } else {
438
                        status = deployments::DeploymentStatus::Success;
7✔
439
                }
440
        }
441

442
        // Push status.
443
        auto err = ctx.deployment_client->PushStatus(
140✔
444
                ctx.deployment.state_data->update_info.id,
140✔
445
                status,
446
                "",
447
                ctx.mender_context.GetConfig().server_url,
280✔
448
                ctx.http_client,
449
                [result_handler, &ctx](error::Error err) {
412✔
450
                        // If there is an error, we don't submit logs now, but call the handler,
451
                        // which may schedule a retry later. If there is no error, and the
452
                        // deployment as a whole was successful, then also call the handler here,
453
                        // since we don't need to submit logs at all then.
454
                        if (err != error::NoError || !ctx.deployment.failed) {
140✔
455
                                result_handler(err);
101✔
456
                                return;
101✔
457
                        }
458

459
                        // Push logs.
460
                        err = ctx.deployment_client->PushLogs(
78✔
461
                                ctx.deployment.state_data->update_info.id,
39✔
462
                                ctx.deployment.logger->LogFilePath(),
78✔
463
                                ctx.mender_context.GetConfig().server_url,
39✔
464
                                ctx.http_client,
465
                                result_handler);
117✔
466

467
                        if (err != error::NoError) {
39✔
468
                                result_handler(err);
×
469
                        }
470
                });
560✔
471

472
        if (err != error::NoError) {
140✔
473
                result_handler(err);
×
474
        }
475

476
        // No action, wait for reply from status endpoint.
477
}
140✔
478

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

482
        DefaultAsyncErrorHandler(
21✔
483
                poster,
484
                ctx.deployment.update_module->AsyncArtifactInstall(
21✔
485
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
486
}
21✔
487

488
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
36✔
489
        DefaultAsyncErrorHandler(
36✔
490
                poster,
491
                ctx.deployment.update_module->AsyncNeedsReboot(
36✔
492
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
140✔
493
                                if (!reboot_action.has_value()) {
36✔
494
                                        log::Error(reboot_action.error().String());
2✔
495
                                        poster.PostEvent(StateEvent::Failure);
2✔
496
                                        return;
2✔
497
                                }
498

499
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
34✔
500
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
34✔
501
                                        NeedsRebootToDbString(*reboot_action);
68✔
502
                                switch (*reboot_action) {
34✔
503
                                case update_module::RebootAction::No:
4✔
504
                                        poster.PostEvent(StateEvent::NothingToDo);
4✔
505
                                        break;
4✔
506
                                case update_module::RebootAction::Yes:
30✔
507
                                case update_module::RebootAction::Automatic:
508
                                        poster.PostEvent(StateEvent::Success);
30✔
509
                                        break;
30✔
510
                                }
511
                        }));
72✔
512
}
36✔
513

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

517
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
13✔
518
        auto exp_reboot_mode =
519
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
26✔
520
        // Should always be true because we check it at load time.
521
        assert(exp_reboot_mode);
13✔
522

523
        switch (exp_reboot_mode.value()) {
13✔
524
        case update_module::RebootAction::No:
×
525
                // Should not happen because then we don't enter this state.
526
                assert(false);
×
527
                poster.PostEvent(StateEvent::Failure);
528
                break;
529
        case update_module::RebootAction::Yes:
13✔
530
                DefaultAsyncErrorHandler(
13✔
531
                        poster,
532
                        ctx.deployment.update_module->AsyncArtifactReboot(
13✔
533
                                ctx.event_loop, DefaultStateHandler {poster}));
26✔
534
                break;
13✔
535
        case update_module::RebootAction::Automatic:
×
536
                DefaultAsyncErrorHandler(
×
537
                        poster,
538
                        ctx.deployment.update_module->AsyncSystemReboot(
×
539
                                ctx.event_loop, DefaultStateHandler {poster}));
×
540
                break;
×
541
        }
542
}
13✔
543

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

547
        DefaultAsyncErrorHandler(
14✔
548
                poster,
549
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
14✔
550
                        ctx.event_loop, DefaultStateHandler {poster}));
28✔
551
}
14✔
552

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

558
        poster.PostEvent(StateEvent::Success);
10✔
559
}
10✔
560

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

564
        DefaultAsyncErrorHandler(
8✔
565
                poster,
566
                ctx.deployment.update_module->AsyncArtifactCommit(
8✔
567
                        ctx.event_loop, DefaultStateHandler {poster}));
16✔
568
}
8✔
569

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

574
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
575
        assert(ctx.deployment.state_data);
6✔
576
        auto &state_data = *ctx.deployment.state_data;
6✔
577
        if (state_data.update_info.has_db_schema_update) {
6✔
578
                state_data.update_info.has_db_schema_update = false;
1✔
579
                auto err = ctx.SaveDeploymentStateData(state_data);
1✔
580
                if (err != error::NoError) {
1✔
581
                        log::Error("Not able to commit schema update: " + err.String());
×
582
                        poster.PostEvent(StateEvent::Failure);
×
583
                        return;
×
584
                }
585
        }
586

587
        poster.PostEvent(StateEvent::Success);
6✔
588
}
589

590
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
23✔
591
        DefaultAsyncErrorHandler(
23✔
592
                poster,
593
                ctx.deployment.update_module->AsyncSupportsRollback(
23✔
594
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
88✔
595
                                if (!rollback_supported.has_value()) {
23✔
596
                                        log::Error(rollback_supported.error().String());
1✔
597
                                        poster.PostEvent(StateEvent::Failure);
1✔
598
                                        return;
1✔
599
                                }
600

601
                                ctx.deployment.state_data->update_info.supports_rollback =
22✔
602
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
603
                                if (*rollback_supported) {
22✔
604
                                        poster.PostEvent(StateEvent::RollbackStarted);
20✔
605
                                        poster.PostEvent(StateEvent::Success);
20✔
606
                                } else {
607
                                        poster.PostEvent(StateEvent::NothingToDo);
2✔
608
                                }
609
                        }));
46✔
610
}
23✔
611

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

615
        DefaultAsyncErrorHandler(
21✔
616
                poster,
617
                ctx.deployment.update_module->AsyncArtifactRollback(
21✔
618
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
619
}
21✔
620

621
void UpdateRollbackRebootState::OnEnterSaveState(
29✔
622
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
623
        log::Debug("Entering ArtifactRollbackReboot state");
29✔
624

625
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
626
        // succeeds.
627
        auto err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(
628
                ctx.event_loop, [&poster](error::Error err) {
58✔
629
                        if (err != error::NoError) {
29✔
630
                                log::Error(err.String());
1✔
631
                        }
632
                        poster.PostEvent(StateEvent::Success);
29✔
633
                });
87✔
634

635
        if (err != error::NoError) {
29✔
636
                log::Error(err.String());
×
637
                poster.PostEvent(StateEvent::Success);
×
638
        }
639
}
29✔
640

641
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
30✔
642
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
643
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
30✔
644

645
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
646
        // state loop detection will eventually kick in.
647
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
648
                ctx.event_loop, [&poster](error::Error err) {
60✔
649
                        if (err != error::NoError) {
30✔
650
                                log::Error(err.String());
12✔
651
                                poster.PostEvent(StateEvent::Retry);
12✔
652
                                return;
12✔
653
                        }
654
                        poster.PostEvent(StateEvent::Success);
18✔
655
                });
60✔
656
        if (err != error::NoError) {
30✔
657
                log::Error(err.String());
×
658
                poster.PostEvent(StateEvent::Retry);
×
659
        }
660
}
30✔
661

662
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
27✔
663
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
27✔
664
        poster.PostEvent(StateEvent::Success);
27✔
665
}
27✔
666

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

670
        DefaultAsyncErrorHandler(
27✔
671
                poster,
672
                ctx.deployment.update_module->AsyncArtifactFailure(
27✔
673
                        ctx.event_loop, DefaultStateHandler {poster}));
54✔
674
}
27✔
675

676
static string AddInconsistentSuffix(const string &str) {
8✔
677
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
8✔
678
        // `string::ends_with` is C++20... grumble
679
        string ret {str};
8✔
680
        if (!common::EndsWith(ret, suffix)) {
8✔
681
                ret.append(suffix);
8✔
682
        }
683
        return ret;
8✔
684
}
685

686
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
34✔
687
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
34✔
688
                // If the update failed, but we rolled back successfully, then we don't need to do
689
                // anything, just keep the old data.
690
                poster.PostEvent(StateEvent::Success);
21✔
691
                return;
21✔
692
        }
693

694
        assert(ctx.deployment.state_data);
13✔
695
        // This state should never happen: rollback failed, but update not failed??
696
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
13✔
697

698
        // We expect Cleanup to be the next state after this.
699
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
13✔
700

701
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
13✔
702

703
        string artifact_name;
13✔
704
        if (ctx.deployment.rollback_failed) {
13✔
705
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
6✔
706
        } else {
707
                artifact_name = artifact.artifact_name;
7✔
708
        }
709

710
        auto err = ctx.mender_context.CommitArtifactData(
13✔
711
                artifact_name,
712
                artifact.artifact_group,
13✔
713
                artifact.type_info_provides,
13✔
714
                artifact.clears_artifact_provides,
13✔
715
                [&ctx](kv_db::Transaction &txn) {
13✔
716
                        // Save the Cleanup state together with the artifact data, atomically.
717
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
13✔
718
                });
26✔
719
        if (err != error::NoError) {
13✔
720
                log::Error("Error saving artifact data: " + err.String());
×
721
                if (err.code
×
722
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
723
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
724
                        return;
×
725
                }
726
                poster.PostEvent(StateEvent::Failure);
×
727
                return;
×
728
        }
729

730
        poster.PostEvent(StateEvent::Success);
13✔
731
}
732

733
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
44✔
734
        log::Debug("Entering ArtifactCleanup state");
44✔
735

736
        // It's possible for there not to be an initialized update_module structure, if the
737
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
738
        // no-op.
739
        if (!ctx.deployment.update_module) {
44✔
740
                poster.PostEvent(StateEvent::Success);
6✔
741
                return;
6✔
742
        }
743

744
        DefaultAsyncErrorHandler(
38✔
745
                poster,
746
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
76✔
747
}
748

749
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
46✔
750
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
46✔
751
                // Remove state data, since we're done now.
752
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
88✔
753
                if (err != error::NoError) {
44✔
754
                        return err;
×
755
                }
756
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
44✔
757
        });
46✔
758
        if (err != error::NoError) {
46✔
759
                log::Error("Error removing artifact data: " + err.String());
2✔
760
                poster.PostEvent(StateEvent::Failure);
2✔
761
                return;
2✔
762
        }
763

764
        poster.PostEvent(StateEvent::Success);
44✔
765
}
766

767
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
768
        assert(ctx.deployment.state_data);
2✔
769
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
2✔
770

771
        // Mark update as inconsistent.
772
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
773

774
        auto err = ctx.mender_context.CommitArtifactData(
2✔
775
                artifact_name,
776
                artifact.artifact_group,
2✔
777
                artifact.type_info_provides,
2✔
778
                artifact.clears_artifact_provides,
2✔
779
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
780
        if (err != error::NoError) {
2✔
781
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
782
                poster.PostEvent(StateEvent::Failure);
×
783
                return;
×
784
        }
785

786
        poster.PostEvent(StateEvent::Success);
2✔
787
}
788

789
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
46✔
790
        ctx.FinishDeploymentLogging();
46✔
791

792
        ctx.deployment = {};
46✔
793
        poster.PostEvent(StateEvent::DeploymentEnded);
46✔
794
        poster.PostEvent(StateEvent::Success);
46✔
795
}
46✔
796

797
ExitState::ExitState(events::EventLoop &event_loop) :
×
798
        event_loop_(event_loop) {
×
799
}
×
800

801
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
46✔
802
        event_loop_.Stop();
46✔
803
}
46✔
804

805
namespace deployment_tracking {
806

807
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
32✔
808
        ctx.deployment.failed = false;
32✔
809
        ctx.deployment.rollback_failed = false;
32✔
810
}
32✔
811

812
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
32✔
813
        ctx.deployment.failed = true;
32✔
814
        ctx.deployment.rollback_failed = true;
32✔
815
}
32✔
816

817
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
26✔
818
        ctx.deployment.failed = true;
26✔
819
        ctx.deployment.rollback_failed = false;
26✔
820
}
26✔
821

822
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
6✔
823
        ctx.deployment.failed = true;
6✔
824
        ctx.deployment.rollback_failed = true;
6✔
825
}
6✔
826

827
} // namespace deployment_tracking
828

829
} // namespace daemon
830
} // namespace update
831
} // 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