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

mendersoftware / mender / 1056184062

31 Oct 2023 12:41PM UTC coverage: 80.182% (+0.3%) from 79.877%
1056184062

push

gitlab-ci

kacf
chore: Do not try to resume requests that were cancelled by callers.

This requires us to move the destruction of the inner reader until
after the body handler has been called, otherwise it will trigger
another `operation_canceled` error which we don't want.

Ticket: MEN-6807

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

5 of 5 new or added lines in 1 file covered. (100.0%)

6890 of 8593 relevant lines covered (80.18%)

9363.89 hits per line

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

84.8
/src/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_,
74
                this->action_,
75
                [state_name, &poster](error::Error err) {
7,821✔
76
                        if (err != error::NoError) {
1,036✔
77
                                log::Error(
21✔
78
                                        "Received error: (" + err.String() + ") when running the State Script scripts "
42✔
79
                                        + state_name);
63✔
80
                                poster.PostEvent(StateEvent::Failure);
21✔
81
                                return;
21✔
82
                        }
83
                        log::Debug("Successfully ran the " + state_name + " State Scripts...");
2,030✔
84
                        poster.PostEvent(StateEvent::Success);
1,015✔
85
                },
86
                this->on_error_);
2,072✔
87

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

97

98
void SaveStateScriptState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
278✔
99
        return state_script_state_.OnEnter(ctx, poster);
278✔
100
}
101

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

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

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

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

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

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

145
        DoSubmitInventory(ctx, poster);
57✔
146
}
57✔
147

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

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

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

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

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

190
                        ctx.BeginDeploymentLogging();
55✔
191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

513
        // Push status.
514
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
522✔
515
        auto err = ctx.deployment_client->PushStatus(
516
                ctx.deployment.state_data->update_info.id,
261✔
517
                status,
518
                "",
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.http_client,
535
                                result_handler);
73✔
536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

858
        ctx.deployment = {};
91✔
859
        poster.PostEvent(
91✔
860
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
91✔
861
        poster.PostEvent(StateEvent::DeploymentEnded);
91✔
862
        poster.PostEvent(StateEvent::Success);
91✔
863
}
91✔
864

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

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

873
namespace deployment_tracking {
874

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

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

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

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

895
} // namespace deployment_tracking
896

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