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

mendersoftware / mender / 1006302423

15 Sep 2023 12:56PM UTC coverage: 78.737% (+0.3%) from 78.482%
1006302423

push

gitlab-ci

oleorhagen
feat(daemon): Add state script support

Ticket: MEN-6637
Changelog: None

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

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

5984 of 7600 relevant lines covered (78.74%)

1109.89 hits per line

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

82.31
/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 kv_db = mender::common::key_value_database;
33
namespace path = mender::common::path;
34
namespace log = mender::common::log;
35

36
namespace main_context = mender::update::context;
37
namespace inventory = mender::update::inventory;
38

39
class DefaultStateHandler {
40
public:
41
        void operator()(const error::Error &err) {
274✔
42
                if (err != error::NoError) {
274✔
43
                        log::Error(err.String());
23✔
44
                        poster.PostEvent(StateEvent::Failure);
23✔
45
                        return;
23✔
46
                }
47
                poster.PostEvent(StateEvent::Success);
251✔
48
        }
49

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

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

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

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

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

85
        if (err != error::NoError) {
978✔
86
                log::Error(
×
87
                        "Failed to schedule the state script execution for: " + state_name
×
88
                        + " got error: " + err.String());
×
89
                poster.PostEvent(StateEvent::Failure);
×
90
                return;
×
91
        }
92
}
93

94

95
void SaveStateScriptState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
262✔
96
        return state_script_state_.OnEnter(ctx, poster);
262✔
97
}
98

99
void IdleState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
110✔
100
        log::Debug("Entering Idle state");
110✔
101
}
110✔
102

103
void SubmitInventoryState::DoSubmitInventory(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
104
        log::Debug("Submitting inventory");
54✔
105

106
        auto handler = [&poster](error::Error err) {
108✔
107
                if (err != error::NoError) {
54✔
108
                        log::Error("Failed to submit inventory: " + err.String());
1✔
109
                }
110
                poster.PostEvent((err == error::NoError) ? StateEvent::Success : StateEvent::Failure);
54✔
111
        };
54✔
112

113
        auto err = ctx.inventory_client->PushData(
54✔
114
                ctx.mender_context.GetConfig().paths.GetInventoryScriptsDir(),
108✔
115
                ctx.mender_context.GetConfig().server_url,
54✔
116
                ctx.event_loop,
117
                ctx.http_client,
118
                handler);
162✔
119

120
        if (err != error::NoError) {
54✔
121
                // This is the only case the handler won't be called for us by
122
                // PushData() (see inventory::PushInventoryData()).
123
                handler(err);
1✔
124
        }
125
}
54✔
126

127
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
128
        // Schedule timer for next update first, so that long running submissions do not postpone
129
        // the schedule.
130
        log::Debug(
54✔
131
                "Scheduling the next inventory submission in: "
132
                + to_string(ctx.mender_context.GetConfig().inventory_poll_interval_seconds) + " seconds");
108✔
133
        poll_timer_.AsyncWait(
54✔
134
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
54✔
135
                [&poster](error::Error err) {
2✔
136
                        if (err != error::NoError) {
1✔
137
                                log::Error("Inventory poll timer caused error: " + err.String());
×
138
                        } else {
139
                                poster.PostEvent(StateEvent::InventoryPollingTriggered);
1✔
140
                        }
141
                });
109✔
142

143
        DoSubmitInventory(ctx, poster);
54✔
144
}
54✔
145

146
void PollForDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
147
        log::Debug("Polling for update");
54✔
148

149
        // Schedule timer for next update first, so that long running submissions do not postpone
150
        // the schedule.
151
        log::Debug(
54✔
152
                "Scheduling the next deployment check in: "
153
                + to_string(ctx.mender_context.GetConfig().update_poll_interval_seconds) + " seconds");
108✔
154
        poll_timer_.AsyncWait(
54✔
155
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
54✔
156
                [&poster](error::Error err) {
×
157
                        if (err != error::NoError) {
×
158
                                log::Error("Update poll timer caused error: " + err.String());
×
159
                        } else {
160
                                poster.PostEvent(StateEvent::DeploymentPollingTriggered);
×
161
                        }
162
                });
108✔
163

164
        auto err = ctx.deployment_client->CheckNewDeployments(
54✔
165
                ctx.mender_context,
166
                ctx.mender_context.GetConfig().server_url,
108✔
167
                ctx.http_client,
168
                [&ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
312✔
169
                        if (!response) {
52✔
170
                                log::Error("Error while polling for deployment: " + response.error().String());
×
171
                                poster.PostEvent(StateEvent::Failure);
×
172
                                return;
×
173
                        } else if (!response.value()) {
52✔
174
                                log::Info("No update available");
×
175
                                poster.PostEvent(StateEvent::NothingToDo);
×
176
                                return;
×
177
                        }
178

179
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
52✔
180
                        if (!exp_data) {
52✔
181
                                log::Error("Error in API response: " + exp_data.error().String());
×
182
                                poster.PostEvent(StateEvent::Failure);
×
183
                                return;
×
184
                        }
185

186
                        // Make a new set of update data.
187
                        ctx.deployment.state_data.reset(new StateData(std::move(exp_data.value())));
52✔
188

189
                        ctx.BeginDeploymentLogging();
52✔
190

191
                        log::Info("Running Mender client " + conf::kMenderVersion);
52✔
192
                        log::Info(
52✔
193
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
104✔
194

195
                        poster.PostEvent(StateEvent::DeploymentStarted);
52✔
196
                        poster.PostEvent(StateEvent::Success);
52✔
197
                });
108✔
198

199
        if (err != error::NoError) {
54✔
200
                log::Error("Error when trying to poll for deployment: " + err.String());
2✔
201
                poster.PostEvent(StateEvent::Failure);
2✔
202
        }
203
}
54✔
204

205
void SaveState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
503✔
206
        assert(ctx.deployment.state_data);
503✔
207

208
        ctx.deployment.state_data->state = DatabaseStateString();
503✔
209

210
        log::Trace("Storing deployment state in the DB (database-string):" + DatabaseStateString());
503✔
211

212
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
503✔
213
        if (err != error::NoError) {
503✔
214
                log::Error(err.String());
10✔
215
                if (err.code
10✔
216
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
10✔
217
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
218
                        return;
1✔
219
                } else if (!IsFailureState()) {
9✔
220
                        // Non-failure states should be interrupted, but failure states should be
221
                        // allowed to do their work, even if a database error was detected.
222
                        poster.PostEvent(StateEvent::Failure);
2✔
223
                        return;
2✔
224
                }
225
        }
226

227
        OnEnterSaveState(ctx, poster);
500✔
228
}
229

230
void UpdateDownloadState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
231
        log::Debug("Entering Download state");
50✔
232

233
        auto req = make_shared<http::OutgoingRequest>();
50✔
234
        req->SetMethod(http::Method::GET);
50✔
235
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
50✔
236
        if (err != error::NoError) {
50✔
237
                log::Error(err.String());
×
238
                poster.PostEvent(StateEvent::Failure);
×
239
                return;
×
240
        }
241

242
        err = ctx.download_client.AsyncCall(
50✔
243
                req,
244
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
199✔
245
                        if (!exp_resp) {
50✔
246
                                log::Error(exp_resp.error().String());
×
247
                                poster.PostEvent(StateEvent::Failure);
×
248
                                return;
1✔
249
                        }
250

251
                        auto &resp = exp_resp.value();
50✔
252
                        if (resp->GetStatusCode() != http::StatusOK) {
50✔
253
                                log::Error(
1✔
254
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
255
                                ctx.download_client.Cancel();
1✔
256
                                poster.PostEvent(StateEvent::Failure);
1✔
257
                                return;
1✔
258
                        }
259

260
                        auto http_reader = resp->MakeBodyAsyncReader();
49✔
261
                        if (!http_reader) {
49✔
262
                                log::Error(http_reader.error().String());
×
263
                                ctx.download_client.Cancel();
×
264
                                poster.PostEvent(StateEvent::Failure);
×
265
                                return;
×
266
                        }
267
                        ctx.deployment.artifact_reader =
268
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
49✔
269
                        ParseArtifact(ctx, poster);
49✔
270
                },
271
                [](http::ExpectedIncomingResponsePtr exp_resp) {
50✔
272
                        if (!exp_resp) {
50✔
273
                                log::Error(exp_resp.error().String());
50✔
274
                                // Cannot handle error here, because this handler is called at the
275
                                // end of the download, when we have already left this state. So
276
                                // rely on this error being propagated through the BodyAsyncReader
277
                                // above instead.
278
                                return;
50✔
279
                        }
280
                });
100✔
281

282
        if (err != error::NoError) {
50✔
283
                log::Error(err.String());
×
284
                poster.PostEvent(StateEvent::Failure);
×
285
                return;
×
286
        }
287
}
288

289
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
49✔
290
        artifact::config::ParserConfig config {
49✔
291
                .artifact_scripts_filesystem_path =
292
                        ctx.mender_context.GetConfig().paths.GetArtScriptsPath(),
49✔
293
                .artifact_scripts_version = 3,
294
        };
49✔
295
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
49✔
296
        if (!exp_parser) {
49✔
297
                log::Error(exp_parser.error().String());
×
298
                poster.PostEvent(StateEvent::Failure);
×
299
                return;
×
300
        }
301
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
49✔
302

303
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
49✔
304
        if (!exp_header) {
49✔
305
                log::Error(exp_header.error().String());
×
306
                poster.PostEvent(StateEvent::Failure);
×
307
                return;
×
308
        }
309
        auto &header = exp_header.value();
49✔
310

311
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
49✔
312
        if (!exp_matches) {
49✔
313
                log::Error(exp_matches.error().String());
2✔
314
                poster.PostEvent(StateEvent::Failure);
2✔
315
                return;
2✔
316
        } else if (!exp_matches.value()) {
47✔
317
                // reasons already logged
318
                poster.PostEvent(StateEvent::Failure);
1✔
319
                return;
1✔
320
        }
321

322
        log::Info("Installing artifact...");
46✔
323

324
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
46✔
325

326
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
46✔
327

328
        assert(ctx.deployment.state_data->update_info.artifact.payload_types.size() == 1);
46✔
329

330
        // Initial state data save, now that we have enough information from the artifact.
331
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
46✔
332
        if (err != error::NoError) {
46✔
333
                log::Error(err.String());
×
334
                if (err.code
×
335
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
336
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
337
                        return;
×
338
                } else {
339
                        poster.PostEvent(StateEvent::Failure);
×
340
                        return;
×
341
                }
342
        }
343

344
        if (header.header.payload_type == "") {
46✔
345
                // Empty-payload-artifact, aka "bootstrap artifact".
346
                poster.PostEvent(StateEvent::NothingToDo);
1✔
347
                return;
1✔
348
        }
349

350
        ctx.deployment.update_module.reset(
45✔
351
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
45✔
352

353
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
45✔
354
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
90✔
355
        if (err != error::NoError) {
45✔
356
                log::Error(err.String());
×
357
                poster.PostEvent(StateEvent::Failure);
×
358
                return;
×
359
        }
360

361
        auto exp_payload = ctx.deployment.artifact_parser->Next();
45✔
362
        if (!exp_payload) {
45✔
363
                log::Error(exp_payload.error().String());
×
364
                poster.PostEvent(StateEvent::Failure);
×
365
                return;
×
366
        }
367
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
45✔
368

369
        ctx.deployment.update_module->AsyncDownload(
90✔
370
                ctx.event_loop, *ctx.deployment.artifact_payload, [&poster](error::Error err) {
45✔
371
                        if (err != error::NoError) {
45✔
372
                                log::Error(err.String());
2✔
373
                                poster.PostEvent(StateEvent::Failure);
2✔
374
                                return;
2✔
375
                        }
376

377
                        poster.PostEvent(StateEvent::Success);
43✔
378
                });
90✔
379
}
380

381
SendStatusUpdateState::SendStatusUpdateState(
×
382
        optional::optional<deployments::DeploymentStatus> status) :
264✔
383
        status_(status),
384
        mode_(FailureMode::Ignore) {
×
385
}
×
386

387
SendStatusUpdateState::SendStatusUpdateState(
×
388
        optional::optional<deployments::DeploymentStatus> status,
389
        events::EventLoop &event_loop,
390
        int retry_interval_seconds) :
176✔
391
        status_(status),
392
        mode_(FailureMode::RetryThenFail),
393
        // MEN-2676: Cap at 10 retries.
394
        retry_(
395
                Retry {http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), 10), event_loop}) {
×
396
}
×
397

398
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
168✔
399
        if (retry_) {
168✔
400
                retry_->backoff.SetSmallestInterval(interval);
168✔
401
        }
402
}
168✔
403

404
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
224✔
405
        // Reset this every time we enter the state, which means a new round of retries.
406
        if (retry_) {
224✔
407
                retry_->backoff.Reset();
105✔
408
        }
409

410
        DoStatusUpdate(ctx, poster);
224✔
411
}
224✔
412

413
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
243✔
414
        assert(ctx.deployment_client);
243✔
415
        assert(ctx.deployment.state_data);
243✔
416

417
        log::Info("Sending status update to server");
243✔
418

419
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
549✔
420
                if (err != error::NoError) {
243✔
421
                        log::Error("Could not send deployment status: " + err.String());
24✔
422

423
                        switch (mode_) {
24✔
424
                        case FailureMode::Ignore:
3✔
425
                                break;
3✔
426
                        case FailureMode::RetryThenFail:
21✔
427
                                if (err.code
21✔
428
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
429
                                        // If the deployment was aborted upstream it is an immediate
430
                                        // failure, even if retry is enabled.
431
                                        poster.PostEvent(StateEvent::Failure);
1✔
432
                                        return;
21✔
433
                                }
434

435
                                auto exp_interval = retry_->backoff.NextInterval();
40✔
436
                                if (!exp_interval) {
20✔
437
                                        log::Error(
1✔
438
                                                "Giving up on sending status updates to server: "
439
                                                + exp_interval.error().String());
2✔
440
                                        poster.PostEvent(StateEvent::Failure);
1✔
441
                                        return;
1✔
442
                                }
443

444
                                log::Info(
19✔
445
                                        "Retrying status update after "
446
                                        + to_string(chrono::milliseconds(*exp_interval).count() / 1000) + " seconds");
38✔
447

448
                                retry_->wait_timer.AsyncWait(
19✔
449
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
19✔
450
                                                // Error here is quite unexpected (from a timer), so treat
451
                                                // this as an immediate error, despite Retry flag.
452
                                                if (err != error::NoError) {
19✔
453
                                                        log::Error(
×
454
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
455
                                                                + err.String());
×
456
                                                        poster.PostEvent(StateEvent::Failure);
×
457
                                                        return;
×
458
                                                }
459

460
                                                // Try again. Since both status and logs are sent
461
                                                // from here, there's a chance this might resubmit
462
                                                // the status, but there's no harm in it, and it
463
                                                // won't happen often.
464
                                                DoStatusUpdate(ctx, poster);
19✔
465
                                        });
38✔
466
                                return;
19✔
467
                        }
468
                }
469

470
                poster.PostEvent(StateEvent::Success);
222✔
471
        };
243✔
472

473
        deployments::DeploymentStatus status;
474
        if (status_) {
243✔
475
                status = status_.value();
157✔
476
        } else {
477
                // If nothing is specified, grab success/failure status from the deployment status.
478
                if (ctx.deployment.failed) {
86✔
479
                        status = deployments::DeploymentStatus::Failure;
72✔
480
                } else {
481
                        status = deployments::DeploymentStatus::Success;
14✔
482
                }
483
        }
484

485
        // Push status.
486
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
243✔
487
        auto err = ctx.deployment_client->PushStatus(
243✔
488
                ctx.deployment.state_data->update_info.id,
243✔
489
                status,
490
                "",
491
                ctx.mender_context.GetConfig().server_url,
486✔
492
                ctx.http_client,
493
                [result_handler, &ctx](error::Error err) {
750✔
494
                        // If there is an error, we don't submit logs now, but call the handler,
495
                        // which may schedule a retry later. If there is no error, and the
496
                        // deployment as a whole was successful, then also call the handler here,
497
                        // since we don't need to submit logs at all then.
498
                        if (err != error::NoError || !ctx.deployment.failed) {
243✔
499
                                result_handler(err);
171✔
500
                                return;
171✔
501
                        }
502

503
                        // Push logs.
504
                        err = ctx.deployment_client->PushLogs(
144✔
505
                                ctx.deployment.state_data->update_info.id,
72✔
506
                                ctx.deployment.logger->LogFilePath(),
144✔
507
                                ctx.mender_context.GetConfig().server_url,
72✔
508
                                ctx.http_client,
509
                                result_handler);
216✔
510

511
                        if (err != error::NoError) {
72✔
512
                                result_handler(err);
×
513
                        }
514
                });
972✔
515

516
        if (err != error::NoError) {
243✔
517
                result_handler(err);
×
518
        }
519

520
        // No action, wait for reply from status endpoint.
521
}
243✔
522

523
void UpdateInstallState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
39✔
524
        log::Debug("Entering ArtifactInstall state");
39✔
525

526
        DefaultAsyncErrorHandler(
39✔
527
                poster,
528
                ctx.deployment.update_module->AsyncArtifactInstall(
39✔
529
                        ctx.event_loop, DefaultStateHandler {poster}));
78✔
530
}
39✔
531

532
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
69✔
533
        DefaultAsyncErrorHandler(
69✔
534
                poster,
535
                ctx.deployment.update_module->AsyncNeedsReboot(
69✔
536
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
272✔
537
                                if (!reboot_action.has_value()) {
69✔
538
                                        log::Error(reboot_action.error().String());
2✔
539
                                        poster.PostEvent(StateEvent::Failure);
2✔
540
                                        return;
2✔
541
                                }
542

543
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
67✔
544
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
67✔
545
                                        NeedsRebootToDbString(*reboot_action);
134✔
546
                                switch (*reboot_action) {
67✔
547
                                case update_module::RebootAction::No:
8✔
548
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
549
                                        break;
8✔
550
                                case update_module::RebootAction::Yes:
59✔
551
                                case update_module::RebootAction::Automatic:
552
                                        poster.PostEvent(StateEvent::Success);
59✔
553
                                        break;
59✔
554
                                }
555
                        }));
138✔
556
}
69✔
557

558
void UpdateRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
24✔
559
        log::Debug("Entering ArtifactReboot state");
24✔
560

561
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
24✔
562
        auto exp_reboot_mode =
563
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
48✔
564
        // Should always be true because we check it at load time.
565
        assert(exp_reboot_mode);
24✔
566

567
        switch (exp_reboot_mode.value()) {
24✔
568
        case update_module::RebootAction::No:
×
569
                // Should not happen because then we don't enter this state.
570
                assert(false);
×
571
                poster.PostEvent(StateEvent::Failure);
572
                break;
573
        case update_module::RebootAction::Yes:
24✔
574
                DefaultAsyncErrorHandler(
24✔
575
                        poster,
576
                        ctx.deployment.update_module->AsyncArtifactReboot(
24✔
577
                                ctx.event_loop, DefaultStateHandler {poster}));
48✔
578
                break;
24✔
579
        case update_module::RebootAction::Automatic:
×
580
                DefaultAsyncErrorHandler(
×
581
                        poster,
582
                        ctx.deployment.update_module->AsyncSystemReboot(
×
583
                                ctx.event_loop, DefaultStateHandler {poster}));
×
584
                break;
×
585
        }
586
}
24✔
587

588
void UpdateVerifyRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
26✔
589
        log::Debug("Entering ArtifactVerifyReboot state");
26✔
590

591
        DefaultAsyncErrorHandler(
26✔
592
                poster,
593
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
26✔
594
                        ctx.event_loop, DefaultStateHandler {poster}));
52✔
595
}
26✔
596

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

602
        poster.PostEvent(StateEvent::Success);
19✔
603
}
19✔
604

605
void UpdateCommitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
15✔
606
        log::Debug("Entering ArtifactCommit state");
15✔
607

608
        DefaultAsyncErrorHandler(
15✔
609
                poster,
610
                ctx.deployment.update_module->AsyncArtifactCommit(
15✔
611
                        ctx.event_loop, DefaultStateHandler {poster}));
30✔
612
}
15✔
613

614
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
15✔
615
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
616
        assert(ctx.deployment.state_data);
15✔
617
        auto &state_data = *ctx.deployment.state_data;
15✔
618
        if (state_data.update_info.has_db_schema_update) {
15✔
619
                state_data.update_info.has_db_schema_update = false;
1✔
620
                auto err = ctx.SaveDeploymentStateData(state_data);
1✔
621
                if (err != error::NoError) {
1✔
622
                        log::Error("Not able to commit schema update: " + err.String());
×
623
                        poster.PostEvent(StateEvent::Failure);
×
624
                        return;
×
625
                }
626
        }
627

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

631
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
44✔
632
        DefaultAsyncErrorHandler(
44✔
633
                poster,
634
                ctx.deployment.update_module->AsyncSupportsRollback(
44✔
635
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
168✔
636
                                if (!rollback_supported.has_value()) {
44✔
637
                                        log::Error(rollback_supported.error().String());
1✔
638
                                        poster.PostEvent(StateEvent::Failure);
1✔
639
                                        return;
1✔
640
                                }
641

642
                                ctx.deployment.state_data->update_info.supports_rollback =
43✔
643
                                        SupportsRollbackToDbString(*rollback_supported);
86✔
644
                                if (*rollback_supported) {
43✔
645
                                        poster.PostEvent(StateEvent::RollbackStarted);
37✔
646
                                        poster.PostEvent(StateEvent::Success);
37✔
647
                                } else {
648
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
649
                                }
650
                        }));
88✔
651
}
44✔
652

653
void UpdateRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
40✔
654
        log::Debug("Entering ArtifactRollback state");
40✔
655

656
        DefaultAsyncErrorHandler(
40✔
657
                poster,
658
                ctx.deployment.update_module->AsyncArtifactRollback(
40✔
659
                        ctx.event_loop, DefaultStateHandler {poster}));
80✔
660
}
40✔
661

662
void UpdateRollbackRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
56✔
663
        log::Debug("Entering ArtifactRollbackReboot state");
56✔
664

665
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
666
        // succeeds.
667
        auto err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(
668
                ctx.event_loop, [&poster](error::Error err) {
112✔
669
                        if (err != error::NoError) {
56✔
670
                                log::Error(err.String());
1✔
671
                        }
672
                        poster.PostEvent(StateEvent::Success);
56✔
673
                });
168✔
674

675
        if (err != error::NoError) {
56✔
676
                log::Error(err.String());
×
677
                poster.PostEvent(StateEvent::Success);
×
678
        }
679
}
56✔
680

681
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
59✔
682
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
683
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
59✔
684

685
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
686
        // state loop detection will eventually kick in.
687
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
688
                ctx.event_loop, [&poster](error::Error err) {
118✔
689
                        if (err != error::NoError) {
59✔
690
                                log::Error(err.String());
22✔
691
                                poster.PostEvent(StateEvent::Retry);
22✔
692
                                return;
22✔
693
                        }
694
                        poster.PostEvent(StateEvent::Success);
37✔
695
                });
118✔
696
        if (err != error::NoError) {
59✔
697
                log::Error(err.String());
×
698
                poster.PostEvent(StateEvent::Retry);
×
699
        }
700
}
59✔
701

702
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
49✔
703
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
49✔
704
        poster.PostEvent(StateEvent::Success);
49✔
705
}
49✔
706

707
void UpdateFailureState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
54✔
708
        log::Debug("Entering ArtifactFailure state");
54✔
709

710
        DefaultAsyncErrorHandler(
54✔
711
                poster,
712
                ctx.deployment.update_module->AsyncArtifactFailure(
54✔
713
                        ctx.event_loop, DefaultStateHandler {poster}));
108✔
714
}
54✔
715

716
static string AddInconsistentSuffix(const string &str) {
20✔
717
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
20✔
718
        // `string::ends_with` is C++20... grumble
719
        string ret {str};
20✔
720
        if (!common::EndsWith(ret, suffix)) {
20✔
721
                ret.append(suffix);
20✔
722
        }
723
        return ret;
20✔
724
}
725

726
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
70✔
727
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
70✔
728
                // If the update failed, but we rolled back successfully, then we don't need to do
729
                // anything, just keep the old data.
730
                poster.PostEvent(StateEvent::Success);
38✔
731
                return;
38✔
732
        }
733

734
        assert(ctx.deployment.state_data);
32✔
735
        // This state should never happen: rollback failed, but update not failed??
736
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
32✔
737

738
        // We expect Cleanup to be the next state after this.
739
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
32✔
740

741
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
32✔
742

743
        string artifact_name;
32✔
744
        if (ctx.deployment.rollback_failed) {
32✔
745
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
18✔
746
        } else {
747
                artifact_name = artifact.artifact_name;
14✔
748
        }
749

750
        auto err = ctx.mender_context.CommitArtifactData(
32✔
751
                artifact_name,
752
                artifact.artifact_group,
32✔
753
                artifact.type_info_provides,
32✔
754
                artifact.clears_artifact_provides,
32✔
755
                [&ctx](kv_db::Transaction &txn) {
32✔
756
                        // Save the Cleanup state together with the artifact data, atomically.
757
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
32✔
758
                });
64✔
759
        if (err != error::NoError) {
32✔
760
                log::Error("Error saving artifact data: " + err.String());
×
761
                if (err.code
×
762
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
763
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
764
                        return;
×
765
                }
766
                poster.PostEvent(StateEvent::Failure);
×
767
                return;
×
768
        }
769

770
        poster.PostEvent(StateEvent::Success);
32✔
771
}
772

773
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
84✔
774
        log::Debug("Entering ArtifactCleanup state");
84✔
775

776
        // It's possible for there not to be an initialized update_module structure, if the
777
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
778
        // no-op.
779
        if (!ctx.deployment.update_module) {
84✔
780
                poster.PostEvent(StateEvent::Success);
8✔
781
                return;
8✔
782
        }
783

784
        DefaultAsyncErrorHandler(
76✔
785
                poster,
786
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
152✔
787
}
788

789
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
86✔
790
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
86✔
791
                // Remove state data, since we're done now.
792
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
168✔
793
                if (err != error::NoError) {
84✔
794
                        return err;
×
795
                }
796
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
84✔
797
        });
86✔
798
        if (err != error::NoError) {
86✔
799
                log::Error("Error removing artifact data: " + err.String());
2✔
800
                poster.PostEvent(StateEvent::Failure);
2✔
801
                return;
2✔
802
        }
803

804
        poster.PostEvent(StateEvent::Success);
84✔
805
}
806

807
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
808
        assert(ctx.deployment.state_data);
2✔
809
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
2✔
810

811
        // Mark update as inconsistent.
812
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
813

814
        auto err = ctx.mender_context.CommitArtifactData(
2✔
815
                artifact_name,
816
                artifact.artifact_group,
2✔
817
                artifact.type_info_provides,
2✔
818
                artifact.clears_artifact_provides,
2✔
819
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
820
        if (err != error::NoError) {
2✔
821
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
822
                poster.PostEvent(StateEvent::Failure);
×
823
                return;
×
824
        }
825

826
        poster.PostEvent(StateEvent::Success);
2✔
827
}
828

829
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
86✔
830
        ctx.FinishDeploymentLogging();
86✔
831

832
        ctx.deployment = {};
86✔
833
        poster.PostEvent(StateEvent::DeploymentEnded);
86✔
834
        poster.PostEvent(StateEvent::Success);
86✔
835
}
86✔
836

837
ExitState::ExitState(events::EventLoop &event_loop) :
×
838
        event_loop_(event_loop) {
×
839
}
×
840

841
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
86✔
842
        event_loop_.Stop();
86✔
843
}
86✔
844

845
namespace deployment_tracking {
846

847
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
848
        ctx.deployment.failed = false;
57✔
849
        ctx.deployment.rollback_failed = false;
57✔
850
}
57✔
851

852
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
853
        ctx.deployment.failed = true;
57✔
854
        ctx.deployment.rollback_failed = true;
57✔
855
}
57✔
856

857
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
51✔
858
        ctx.deployment.failed = true;
51✔
859
        ctx.deployment.rollback_failed = false;
51✔
860
}
51✔
861

862
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
863
        ctx.deployment.failed = true;
12✔
864
        ctx.deployment.rollback_failed = true;
12✔
865
}
12✔
866

867
} // namespace deployment_tracking
868

869
} // namespace daemon
870
} // namespace update
871
} // 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