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

mendersoftware / mender / 1033213046

11 Oct 2023 12:00PM UTC coverage: 80.166% (+0.1%) from 80.029%
1033213046

push

gitlab-ci

kacf
chore: Make it possible to configure repo for no-build situations.

This still allows installing modules, generators, and Documentation,
but does not need any dependencies to be installed except for the
compiler.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6479 of 8082 relevant lines covered (80.17%)

10736.7 hits per line

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

84.87
/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) {
295✔
42
                if (err != error::NoError) {
295✔
43
                        log::Error(err.String());
23✔
44
                        poster.PostEvent(StateEvent::Failure);
23✔
45
                        return;
23✔
46
                }
47
                poster.PostEvent(StateEvent::Success);
272✔
48
        }
49

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

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

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

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

69
void StateScriptState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
1,036✔
70
        string state_name {script_executor::Name(this->state_, this->action_)};
1,036✔
71
        log::Debug("Executing the  " + state_name + " State Scripts...");
2,072✔
72
        auto err = this->script_.AsyncRunScripts(
73
                this->state_, this->action_, [state_name, &poster](error::Error err) {
7,818✔
74
                        if (err != error::NoError) {
1,036✔
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...");
2,030✔
82
                        poster.PostEvent(StateEvent::Success);
1,015✔
83
                });
2,072✔
84

85
        if (err != error::NoError) {
1,036✔
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) {
278✔
96
        return state_script_state_.OnEnter(ctx, poster);
278✔
97
}
98

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

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

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

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

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

127
void SubmitInventoryState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
57✔
128
        // Schedule timer for next update first, so that long running submissions do not postpone
129
        // the schedule.
130
        log::Debug(
57✔
131
                "Scheduling the next inventory submission in: "
132
                + to_string(ctx.mender_context.GetConfig().inventory_poll_interval_seconds) + " seconds");
114✔
133
        poll_timer_.AsyncWait(
57✔
134
                chrono::seconds(ctx.mender_context.GetConfig().inventory_poll_interval_seconds),
57✔
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
                });
1✔
142

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

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

149
        // Schedule timer for next update first, so that long running submissions do not postpone
150
        // the schedule.
151
        log::Debug(
57✔
152
                "Scheduling the next deployment check in: "
153
                + to_string(ctx.mender_context.GetConfig().update_poll_interval_seconds) + " seconds");
114✔
154
        poll_timer_.AsyncWait(
57✔
155
                chrono::seconds(ctx.mender_context.GetConfig().update_poll_interval_seconds),
57✔
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
                });
57✔
163

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

179
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
55✔
180
                        if (!exp_data) {
55✔
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())));
55✔
188

189
                        ctx.BeginDeploymentLogging();
55✔
190

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

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

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

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

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

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

212
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
534✔
213
        if (err != error::NoError) {
534✔
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;
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;
224
                }
225
        }
226

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

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

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

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

251
                        auto &resp = exp_resp.value();
52✔
252
                        if (resp->GetStatusCode() != http::StatusOK) {
52✔
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();
51✔
261
                        if (!http_reader) {
51✔
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());
51✔
269
                        ParseArtifact(ctx, poster);
51✔
270
                },
271
                [](http::ExpectedIncomingResponsePtr exp_resp) {
1✔
272
                        if (!exp_resp) {
1✔
273
                                log::Error(exp_resp.error().String());
1✔
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;
1✔
279
                        }
280
                });
106✔
281

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

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

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

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

323
        log::Info("Installing artifact...");
98✔
324

325
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
49✔
326

327
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
49✔
328

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

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

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

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

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

362
        err = ctx.deployment.update_module->AsyncProvidePayloadFileSizes(
48✔
363
                ctx.event_loop, [&ctx, &poster](expected::ExpectedBool download_with_sizes) {
48✔
364
                        if (!download_with_sizes.has_value()) {
48✔
365
                                log::Error(download_with_sizes.error().String());
×
366
                                poster.PostEvent(StateEvent::Failure);
×
367
                                return;
×
368
                        }
369
                        ctx.deployment.download_with_sizes = download_with_sizes.value();
48✔
370
                        DoDownload(ctx, poster);
48✔
371
                });
48✔
372

373
        if (err != error::NoError) {
48✔
374
                log::Error(err.String());
×
375
                poster.PostEvent(StateEvent::Failure);
×
376
                return;
377
        }
378
}
379

380
void UpdateDownloadState::DoDownload(Context &ctx, sm::EventPoster<StateEvent> &poster) {
48✔
381
        auto exp_payload = ctx.deployment.artifact_parser->Next();
48✔
382
        if (!exp_payload) {
48✔
383
                log::Error(exp_payload.error().String());
×
384
                poster.PostEvent(StateEvent::Failure);
×
385
                return;
386
        }
387
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
48✔
388

389
        auto handler = [&poster](error::Error err) {
96✔
390
                if (err != error::NoError) {
48✔
391
                        log::Error(err.String());
2✔
392
                        poster.PostEvent(StateEvent::Failure);
2✔
393
                        return;
2✔
394
                }
395

396
                poster.PostEvent(StateEvent::Success);
46✔
397
        };
398

399
        if (ctx.deployment.download_with_sizes) {
48✔
400
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
1✔
401
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
1✔
402
        } else {
403
                ctx.deployment.update_module->AsyncDownload(
47✔
404
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
47✔
405
        }
406
}
407

408
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
×
409
        status_(status),
410
        mode_(FailureMode::Ignore) {
×
411
}
×
412

413
SendStatusUpdateState::SendStatusUpdateState(
188✔
414
        optional<deployments::DeploymentStatus> status,
415
        events::EventLoop &event_loop,
416
        int retry_interval_seconds,
417
        int retry_count) :
418
        status_(status),
419
        mode_(FailureMode::RetryThenFail),
420
        retry_(Retry {
188✔
421
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
422
                event_loop}) {
564✔
423
}
188✔
424

425
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
178✔
426
        if (retry_) {
178✔
427
                retry_->backoff.SetSmallestInterval(interval);
178✔
428
        }
429
}
178✔
430

431
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
242✔
432
        // Reset this every time we enter the state, which means a new round of retries.
433
        if (retry_) {
242✔
434
                retry_->backoff.Reset();
435
        }
436

437
        DoStatusUpdate(ctx, poster);
242✔
438
}
242✔
439

440
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
261✔
441
        assert(ctx.deployment_client);
442
        assert(ctx.deployment.state_data);
443

444
        log::Info("Sending status update to server");
522✔
445

446
        auto result_handler = [this, &ctx, &poster](const error::Error &err) {
566✔
447
                if (err != error::NoError) {
261✔
448
                        log::Error("Could not send deployment status: " + err.String());
48✔
449

450
                        switch (mode_) {
24✔
451
                        case FailureMode::Ignore:
452
                                break;
3✔
453
                        case FailureMode::RetryThenFail:
454
                                if (err.code
21✔
455
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
456
                                        // If the deployment was aborted upstream it is an immediate
457
                                        // failure, even if retry is enabled.
458
                                        poster.PostEvent(StateEvent::Failure);
1✔
459
                                        return;
21✔
460
                                }
461

462
                                auto exp_interval = retry_->backoff.NextInterval();
20✔
463
                                if (!exp_interval) {
20✔
464
                                        log::Error(
1✔
465
                                                "Giving up on sending status updates to server: "
466
                                                + exp_interval.error().String());
2✔
467
                                        poster.PostEvent(StateEvent::Failure);
1✔
468
                                        return;
469
                                }
470

471
                                log::Info(
19✔
472
                                        "Retrying status update after "
473
                                        + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count())
38✔
474
                                        + " seconds");
38✔
475
                                retry_->wait_timer.AsyncWait(
19✔
476
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
38✔
477
                                                // Error here is quite unexpected (from a timer), so treat
478
                                                // this as an immediate error, despite Retry flag.
479
                                                if (err != error::NoError) {
19✔
480
                                                        log::Error(
×
481
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
482
                                                                + err.String());
×
483
                                                        poster.PostEvent(StateEvent::Failure);
×
484
                                                        return;
×
485
                                                }
486

487
                                                // Try again. Since both status and logs are sent
488
                                                // from here, there's a chance this might resubmit
489
                                                // the status, but there's no harm in it, and it
490
                                                // won't happen often.
491
                                                DoStatusUpdate(ctx, poster);
19✔
492
                                        });
19✔
493
                                return;
19✔
494
                        }
495
                }
496

497
                poster.PostEvent(StateEvent::Success);
240✔
498
        };
261✔
499

500
        deployments::DeploymentStatus status;
501
        if (status_) {
261✔
502
                status = status_.value();
170✔
503
        } else {
504
                // If nothing is specified, grab success/failure status from the deployment status.
505
                if (ctx.deployment.failed) {
91✔
506
                        status = deployments::DeploymentStatus::Failure;
507
                } else {
508
                        status = deployments::DeploymentStatus::Success;
509
                }
510
        }
511

512
        // Push status.
513
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
522✔
514
        auto err = ctx.deployment_client->PushStatus(
515
                ctx.deployment.state_data->update_info.id,
261✔
516
                status,
517
                "",
518
                ctx.mender_context.GetConfig().server_url,
261✔
519
                ctx.http_client,
520
                [result_handler, &ctx](error::Error err) {
73✔
521
                        // If there is an error, we don't submit logs now, but call the handler,
522
                        // which may schedule a retry later. If there is no error, and the
523
                        // deployment as a whole was successful, then also call the handler here,
524
                        // since we don't need to submit logs at all then.
525
                        if (err != error::NoError || !ctx.deployment.failed) {
261✔
526
                                result_handler(err);
188✔
527
                                return;
188✔
528
                        }
529

530
                        // Push logs.
531
                        err = ctx.deployment_client->PushLogs(
73✔
532
                                ctx.deployment.state_data->update_info.id,
73✔
533
                                ctx.deployment.logger->LogFilePath(),
146✔
534
                                ctx.mender_context.GetConfig().server_url,
73✔
535
                                ctx.http_client,
536
                                result_handler);
73✔
537

538
                        if (err != error::NoError) {
73✔
539
                                result_handler(err);
×
540
                        }
541
                });
522✔
542

543
        if (err != error::NoError) {
261✔
544
                result_handler(err);
×
545
        }
546

547
        // No action, wait for reply from status endpoint.
548
}
261✔
549

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

553
        DefaultAsyncErrorHandler(
42✔
554
                poster,
555
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
556
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
557
}
42✔
558

559
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
560
        DefaultAsyncErrorHandler(
73✔
561
                poster,
562
                ctx.deployment.update_module->AsyncNeedsReboot(
73✔
563
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
144✔
564
                                if (!reboot_action.has_value()) {
73✔
565
                                        log::Error(reboot_action.error().String());
2✔
566
                                        poster.PostEvent(StateEvent::Failure);
2✔
567
                                        return;
2✔
568
                                }
569

570
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
71✔
571
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
572
                                        NeedsRebootToDbString(*reboot_action);
71✔
573
                                switch (*reboot_action) {
71✔
574
                                case update_module::RebootAction::No:
8✔
575
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
576
                                        break;
8✔
577
                                case update_module::RebootAction::Yes:
63✔
578
                                case update_module::RebootAction::Automatic:
579
                                        poster.PostEvent(StateEvent::Success);
63✔
580
                                        break;
63✔
581
                                }
582
                        }));
73✔
583
}
73✔
584

585
void UpdateRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
27✔
586
        log::Debug("Entering ArtifactReboot state");
54✔
587

588
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
589
        auto exp_reboot_mode =
590
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
27✔
591
        // Should always be true because we check it at load time.
592
        assert(exp_reboot_mode);
593

594
        switch (exp_reboot_mode.value()) {
27✔
595
        case update_module::RebootAction::No:
×
596
                // Should not happen because then we don't enter this state.
597
                assert(false);
598
                poster.PostEvent(StateEvent::Failure);
×
599
                break;
600
        case update_module::RebootAction::Yes:
27✔
601
                DefaultAsyncErrorHandler(
27✔
602
                        poster,
603
                        ctx.deployment.update_module->AsyncArtifactReboot(
27✔
604
                                ctx.event_loop, DefaultStateHandler {poster}));
27✔
605
                break;
27✔
606
        case update_module::RebootAction::Automatic:
×
607
                DefaultAsyncErrorHandler(
×
608
                        poster,
609
                        ctx.deployment.update_module->AsyncSystemReboot(
×
610
                                ctx.event_loop, DefaultStateHandler {poster}));
×
611
                break;
×
612
        }
613
}
27✔
614

615
void UpdateVerifyRebootState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
30✔
616
        log::Debug("Entering ArtifactVerifyReboot state");
60✔
617

618
        DefaultAsyncErrorHandler(
30✔
619
                poster,
620
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
30✔
621
                        ctx.event_loop, DefaultStateHandler {poster}));
30✔
622
}
30✔
623

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

629
        poster.PostEvent(StateEvent::Success);
23✔
630
}
23✔
631

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

635
        DefaultAsyncErrorHandler(
19✔
636
                poster,
637
                ctx.deployment.update_module->AsyncArtifactCommit(
19✔
638
                        ctx.event_loop, DefaultStateHandler {poster}));
19✔
639
}
19✔
640

641
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
642
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
643
        assert(ctx.deployment.state_data);
644
        auto &state_data = *ctx.deployment.state_data;
645
        if (state_data.update_info.has_db_schema_update) {
19✔
646
                state_data.update_info.has_db_schema_update = false;
1✔
647
                auto err = ctx.SaveDeploymentStateData(state_data);
1✔
648
                if (err != error::NoError) {
1✔
649
                        log::Error("Not able to commit schema update: " + err.String());
×
650
                        poster.PostEvent(StateEvent::Failure);
×
651
                        return;
652
                }
653
        }
654

655
        poster.PostEvent(StateEvent::Success);
19✔
656
}
657

658
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
45✔
659
        DefaultAsyncErrorHandler(
45✔
660
                poster,
661
                ctx.deployment.update_module->AsyncSupportsRollback(
45✔
662
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
89✔
663
                                if (!rollback_supported.has_value()) {
45✔
664
                                        log::Error(rollback_supported.error().String());
1✔
665
                                        poster.PostEvent(StateEvent::Failure);
1✔
666
                                        return;
1✔
667
                                }
668

669
                                ctx.deployment.state_data->update_info.supports_rollback =
670
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
671
                                if (*rollback_supported) {
44✔
672
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
673
                                        poster.PostEvent(StateEvent::Success);
38✔
674
                                } else {
675
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
676
                                }
677
                        }));
45✔
678
}
45✔
679

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

683
        DefaultAsyncErrorHandler(
41✔
684
                poster,
685
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
686
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
687
}
41✔
688

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

692
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
693
        // succeeds.
694
        auto err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(
695
                ctx.event_loop, [&poster](error::Error err) {
114✔
696
                        if (err != error::NoError) {
57✔
697
                                log::Error(err.String());
2✔
698
                        }
699
                        poster.PostEvent(StateEvent::Success);
57✔
700
                });
114✔
701

702
        if (err != error::NoError) {
57✔
703
                log::Error(err.String());
×
704
                poster.PostEvent(StateEvent::Success);
×
705
        }
706
}
57✔
707

708
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
709
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
710
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
711

712
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
713
        // state loop detection will eventually kick in.
714
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
715
                ctx.event_loop, [&poster](error::Error err) {
120✔
716
                        if (err != error::NoError) {
60✔
717
                                log::Error(err.String());
22✔
718
                                poster.PostEvent(StateEvent::Retry);
22✔
719
                                return;
22✔
720
                        }
721
                        poster.PostEvent(StateEvent::Success);
38✔
722
                });
60✔
723
        if (err != error::NoError) {
60✔
724
                log::Error(err.String());
×
725
                poster.PostEvent(StateEvent::Retry);
×
726
        }
727
}
60✔
728

729
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
730
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
731
        poster.PostEvent(StateEvent::Success);
50✔
732
}
50✔
733

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

737
        DefaultAsyncErrorHandler(
55✔
738
                poster,
739
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
740
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
741
}
55✔
742

743
static string AddInconsistentSuffix(const string &str) {
20✔
744
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
745
        // `string::ends_with` is C++20... grumble
746
        string ret {str};
20✔
747
        if (!common::EndsWith(ret, suffix)) {
20✔
748
                ret.append(suffix);
20✔
749
        }
750
        return ret;
20✔
751
}
752

753
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
754
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
755
                // If the update failed, but we rolled back successfully, then we don't need to do
756
                // anything, just keep the old data.
757
                poster.PostEvent(StateEvent::Success);
39✔
758
                return;
39✔
759
        }
760

761
        assert(ctx.deployment.state_data);
762
        // This state should never happen: rollback failed, but update not failed??
763
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
764

765
        // We expect Cleanup to be the next state after this.
766
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
36✔
767

768
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
769

770
        string artifact_name;
771
        if (ctx.deployment.rollback_failed) {
36✔
772
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
36✔
773
        } else {
774
                artifact_name = artifact.artifact_name;
18✔
775
        }
776

777
        auto err = ctx.mender_context.CommitArtifactData(
36✔
778
                artifact_name,
779
                artifact.artifact_group,
36✔
780
                artifact.type_info_provides,
36✔
781
                artifact.clears_artifact_provides,
36✔
782
                [&ctx](kv_db::Transaction &txn) {
36✔
783
                        // Save the Cleanup state together with the artifact data, atomically.
784
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
36✔
785
                });
72✔
786
        if (err != error::NoError) {
36✔
787
                log::Error("Error saving artifact data: " + err.String());
×
788
                if (err.code
×
789
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
790
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
791
                        return;
792
                }
793
                poster.PostEvent(StateEvent::Failure);
×
794
                return;
795
        }
796

797
        poster.PostEvent(StateEvent::Success);
36✔
798
}
799

800
void UpdateCleanupState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
89✔
801
        log::Debug("Entering ArtifactCleanup state");
178✔
802

803
        // It's possible for there not to be an initialized update_module structure, if the
804
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
805
        // no-op.
806
        if (!ctx.deployment.update_module) {
89✔
807
                poster.PostEvent(StateEvent::Success);
8✔
808
                return;
8✔
809
        }
810

811
        DefaultAsyncErrorHandler(
81✔
812
                poster,
813
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
162✔
814
}
815

816
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
817
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
91✔
818
                // Remove state data, since we're done now.
819
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
89✔
820
                if (err != error::NoError) {
89✔
821
                        return err;
×
822
                }
823
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
89✔
824
        });
91✔
825
        if (err != error::NoError) {
91✔
826
                log::Error("Error removing artifact data: " + err.String());
4✔
827
                poster.PostEvent(StateEvent::Failure);
2✔
828
                return;
829
        }
830

831
        poster.PostEvent(StateEvent::Success);
89✔
832
}
833

834
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
835
        assert(ctx.deployment.state_data);
836
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
837

838
        // Mark update as inconsistent.
839
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
840

841
        auto err = ctx.mender_context.CommitArtifactData(
2✔
842
                artifact_name,
843
                artifact.artifact_group,
2✔
844
                artifact.type_info_provides,
2✔
845
                artifact.clears_artifact_provides,
2✔
846
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
847
        if (err != error::NoError) {
2✔
848
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
849
                poster.PostEvent(StateEvent::Failure);
×
850
                return;
851
        }
852

853
        poster.PostEvent(StateEvent::Success);
2✔
854
}
855

856
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
857
        ctx.FinishDeploymentLogging();
91✔
858

859
        ctx.deployment = {};
91✔
860
        poster.PostEvent(StateEvent::DeploymentEnded);
91✔
861
        poster.PostEvent(StateEvent::Success);
91✔
862
}
91✔
863

864
ExitState::ExitState(events::EventLoop &event_loop) :
94✔
865
        event_loop_(event_loop) {
188✔
866
}
94✔
867

868
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
869
        event_loop_.Stop();
91✔
870
}
91✔
871

872
namespace deployment_tracking {
873

874
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
61✔
875
        ctx.deployment.failed = false;
61✔
876
        ctx.deployment.rollback_failed = false;
61✔
877
}
61✔
878

879
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
58✔
880
        ctx.deployment.failed = true;
58✔
881
        ctx.deployment.rollback_failed = true;
58✔
882
}
58✔
883

884
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
885
        ctx.deployment.failed = true;
52✔
886
        ctx.deployment.rollback_failed = false;
52✔
887
}
52✔
888

889
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
890
        ctx.deployment.failed = true;
12✔
891
        ctx.deployment.rollback_failed = true;
12✔
892
}
12✔
893

894
} // namespace deployment_tracking
895

896
} // namespace daemon
897
} // namespace update
898
} // 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