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

mendersoftware / mender / 1070743650

13 Nov 2023 06:51PM UTC coverage: 80.107% (-0.05%) from 80.157%
1070743650

push

gitlab-ci

oleorhagen
chore: Error when trying to update with empty deployment ID

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

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

65 existing lines in 3 files now uncovered.

7011 of 8752 relevant lines covered (80.11%)

9197.49 hits per line

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

83.17
/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
                                if (err.code != make_error_condition(errc::operation_canceled)) {
×
140
                                        log::Error("Inventory poll timer caused error: " + err.String());
×
141
                                }
142
                        } else {
143
                                poster.PostEvent(StateEvent::InventoryPollingTriggered);
1✔
144
                        }
145
                });
1✔
146

147
        DoSubmitInventory(ctx, poster);
57✔
148
}
57✔
149

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

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

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

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

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

194
                        ctx.BeginDeploymentLogging();
55✔
195

196
                        log::Info("Running Mender client " + conf::kMenderVersion);
165✔
197
                        log::Info(
55✔
198
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
110✔
199

200
                        poster.PostEvent(StateEvent::DeploymentStarted);
55✔
201
                        poster.PostEvent(StateEvent::Success);
55✔
202
                });
57✔
203

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

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

213
        ctx.deployment.state_data->state = DatabaseStateString();
534✔
214

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

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

232
        OnEnterSaveState(ctx, poster);
531✔
233
}
234

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

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

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

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

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

287
        if (err != error::NoError) {
53✔
288
                log::Error(err.String());
×
289
                poster.PostEvent(StateEvent::Failure);
×
290
                return;
291
        }
292
}
293

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

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

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

328
        log::Info("Installing artifact...");
98✔
329

330
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
49✔
331

332
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
49✔
333

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

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

350
        if (header.header.payload_type == "") {
49✔
351
                // Empty-payload-artifact, aka "bootstrap artifact".
352
                poster.PostEvent(StateEvent::NothingToDo);
1✔
353
                return;
354
        }
355

356
        ctx.deployment.update_module.reset(
357
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
48✔
358

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

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

378
        if (err != error::NoError) {
48✔
379
                log::Error(err.String());
×
380
                poster.PostEvent(StateEvent::Failure);
×
381
                return;
382
        }
383
}
384

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

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

401
                poster.PostEvent(StateEvent::Success);
46✔
402
        };
403

404
        if (ctx.deployment.download_with_sizes) {
48✔
405
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
1✔
406
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
1✔
407
        } else {
408
                ctx.deployment.update_module->AsyncDownload(
47✔
409
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
47✔
410
        }
411
}
412

413
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
×
414
        status_(status),
415
        mode_(FailureMode::Ignore) {
×
416
}
×
417

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

430
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
178✔
431
        if (retry_) {
178✔
432
                retry_->backoff.SetSmallestInterval(interval);
178✔
433
        }
434
}
178✔
435

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

442
        DoStatusUpdate(ctx, poster);
242✔
443
}
242✔
444

445
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
261✔
446
        assert(ctx.deployment_client);
447
        assert(ctx.deployment.state_data);
448

449
        log::Info("Sending status update to server");
522✔
450

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

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

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

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

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

502
                poster.PostEvent(StateEvent::Success);
240✔
503
        };
261✔
504

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

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

534
                        // Push logs.
535
                        err = ctx.deployment_client->PushLogs(
73✔
536
                                ctx.deployment.state_data->update_info.id,
73✔
537
                                ctx.deployment.logger->LogFilePath(),
146✔
538
                                ctx.http_client,
539
                                result_handler);
73✔
540

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

546
        if (err != error::NoError) {
261✔
547
                result_handler(err);
×
548
        }
549

550
        // No action, wait for reply from status endpoint.
551
}
261✔
552

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

556
        DefaultAsyncErrorHandler(
42✔
557
                poster,
558
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
559
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
560
}
42✔
561

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

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

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

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

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

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

621
        ctx.deployment.update_module->EnsureRootfsImageFileTree(
30✔
622
                ctx.deployment.update_module->GetUpdateModuleWorkDir());
60✔
623

624
        DefaultAsyncErrorHandler(
30✔
625
                poster,
626
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
30✔
627
                        ctx.event_loop, DefaultStateHandler {poster}));
30✔
628
}
30✔
629

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

635
        poster.PostEvent(StateEvent::Success);
23✔
636
}
23✔
637

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

641
        DefaultAsyncErrorHandler(
19✔
642
                poster,
643
                ctx.deployment.update_module->AsyncArtifactCommit(
19✔
644
                        ctx.event_loop, DefaultStateHandler {poster}));
19✔
645
}
19✔
646

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

661
        poster.PostEvent(StateEvent::Success);
19✔
662
}
663

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

675
                                ctx.deployment.state_data->update_info.supports_rollback =
676
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
677
                                if (*rollback_supported) {
44✔
678
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
679
                                        poster.PostEvent(StateEvent::Success);
38✔
680
                                } else {
681
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
682
                                }
683
                        }));
45✔
684
}
45✔
685

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

689
        DefaultAsyncErrorHandler(
41✔
690
                poster,
691
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
692
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
693
}
41✔
694

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

698
        auto exp_reboot_mode =
699
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
57✔
700
        // Should always be true because we check it at load time.
701
        assert(exp_reboot_mode);
702

703
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
704
        // succeeds.
705
        auto handler = [&poster](error::Error err) {
114✔
706
                if (err != error::NoError) {
57✔
707
                        log::Error(err.String());
2✔
708
                }
709
                poster.PostEvent(StateEvent::Success);
57✔
710
        };
57✔
711

712
        error::Error err;
57✔
713
        switch (exp_reboot_mode.value()) {
57✔
714
        case update_module::RebootAction::No:
715
                // Should not happen because then we don't enter this state.
716
                assert(false);
717

UNCOV
718
                err = error::MakeError(
×
UNCOV
719
                        error::ProgrammingError, "Entered UpdateRollbackRebootState with RebootAction = No");
×
UNCOV
720
                break;
×
721

722
        case update_module::RebootAction::Yes:
57✔
723
                err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(ctx.event_loop, handler);
114✔
724
                break;
57✔
725

UNCOV
726
        case update_module::RebootAction::Automatic:
×
UNCOV
727
                err = ctx.deployment.update_module->AsyncSystemReboot(ctx.event_loop, handler);
×
UNCOV
728
                break;
×
729
        }
730

731
        if (err != error::NoError) {
57✔
UNCOV
732
                log::Error(err.String());
×
UNCOV
733
                poster.PostEvent(StateEvent::Success);
×
734
        }
735
}
57✔
736

737
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
738
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
739
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
740

741
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
742
        // state loop detection will eventually kick in.
743
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
744
                ctx.event_loop, [&poster](error::Error err) {
120✔
745
                        if (err != error::NoError) {
60✔
746
                                log::Error(err.String());
22✔
747
                                poster.PostEvent(StateEvent::Retry);
22✔
748
                                return;
22✔
749
                        }
750
                        poster.PostEvent(StateEvent::Success);
38✔
751
                });
60✔
752
        if (err != error::NoError) {
60✔
UNCOV
753
                log::Error(err.String());
×
UNCOV
754
                poster.PostEvent(StateEvent::Retry);
×
755
        }
756
}
60✔
757

758
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
759
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
760
        poster.PostEvent(StateEvent::Success);
50✔
761
}
50✔
762

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

766
        DefaultAsyncErrorHandler(
55✔
767
                poster,
768
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
769
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
770
}
55✔
771

772
static string AddInconsistentSuffix(const string &str) {
20✔
773
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
774
        // `string::ends_with` is C++20... grumble
775
        string ret {str};
20✔
776
        if (!common::EndsWith(ret, suffix)) {
20✔
777
                ret.append(suffix);
20✔
778
        }
779
        return ret;
20✔
780
}
781

782
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
783
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
784
                // If the update failed, but we rolled back successfully, then we don't need to do
785
                // anything, just keep the old data.
786
                poster.PostEvent(StateEvent::Success);
39✔
787
                return;
39✔
788
        }
789

790
        assert(ctx.deployment.state_data);
791
        // This state should never happen: rollback failed, but update not failed??
792
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
793

794
        // We expect Cleanup to be the next state after this.
795
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
36✔
796

797
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
798

799
        string artifact_name;
800
        if (ctx.deployment.rollback_failed) {
36✔
801
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
36✔
802
        } else {
803
                artifact_name = artifact.artifact_name;
18✔
804
        }
805

806
        bool deploy_failed = ctx.deployment.failed;
36✔
807

808
        // Only the artifact_name and group should be committed in the case of a
809
        // failing update in order to make this consistent with the old client
810
        // behaviour.
811
        auto err = ctx.mender_context.CommitArtifactData(
36✔
812
                artifact_name,
813
                artifact.artifact_group,
36✔
814
                deploy_failed ? nullopt : optional<context::ProvidesData>(artifact.type_info_provides),
72✔
815
                /* Special case: Keep existing provides */
816
                deploy_failed ? context::ClearsProvidesData {}
90✔
817
                                          : optional<context::ClearsProvidesData>(artifact.clears_artifact_provides),
18✔
818
                [&ctx](kv_db::Transaction &txn) {
36✔
819
                        // Save the Cleanup state together with the artifact data, atomically.
820
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
36✔
821
                });
72✔
822
        if (err != error::NoError) {
36✔
823
                log::Error("Error saving artifact data: " + err.String());
×
UNCOV
824
                if (err.code
×
UNCOV
825
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
826
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
827
                        return;
828
                }
UNCOV
829
                poster.PostEvent(StateEvent::Failure);
×
830
                return;
831
        }
832

833
        poster.PostEvent(StateEvent::Success);
36✔
834
}
835

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

839
        // It's possible for there not to be an initialized update_module structure, if the
840
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
841
        // no-op.
842
        if (!ctx.deployment.update_module) {
89✔
843
                poster.PostEvent(StateEvent::Success);
8✔
844
                return;
8✔
845
        }
846

847
        DefaultAsyncErrorHandler(
81✔
848
                poster,
849
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
162✔
850
}
851

852
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
853
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
91✔
854
                // Remove state data, since we're done now.
855
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
89✔
856
                if (err != error::NoError) {
89✔
UNCOV
857
                        return err;
×
858
                }
859
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
89✔
860
        });
91✔
861
        if (err != error::NoError) {
91✔
862
                log::Error("Error removing artifact data: " + err.String());
4✔
863
                poster.PostEvent(StateEvent::Failure);
2✔
864
                return;
865
        }
866

867
        poster.PostEvent(StateEvent::Success);
89✔
868
}
869

870
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
871
        assert(ctx.deployment.state_data);
872
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
873

874
        // Mark update as inconsistent.
875
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
876

877
        auto err = ctx.mender_context.CommitArtifactData(
2✔
878
                artifact_name,
879
                artifact.artifact_group,
2✔
880
                artifact.type_info_provides,
2✔
881
                artifact.clears_artifact_provides,
2✔
882
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
883
        if (err != error::NoError) {
2✔
UNCOV
884
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
UNCOV
885
                poster.PostEvent(StateEvent::Failure);
×
886
                return;
887
        }
888

889
        poster.PostEvent(StateEvent::Success);
2✔
890
}
891

892
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
893
        ctx.FinishDeploymentLogging();
91✔
894

895
        ctx.deployment = {};
91✔
896
        poster.PostEvent(
91✔
897
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
91✔
898
        poster.PostEvent(StateEvent::DeploymentEnded);
91✔
899
        poster.PostEvent(StateEvent::Success);
91✔
900
}
91✔
901

902
ExitState::ExitState(events::EventLoop &event_loop) :
94✔
903
        event_loop_(event_loop) {
188✔
904
}
94✔
905

906
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
907
        event_loop_.Stop();
91✔
908
}
91✔
909

910
namespace deployment_tracking {
911

912
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
61✔
913
        ctx.deployment.failed = false;
61✔
914
        ctx.deployment.rollback_failed = false;
61✔
915
}
61✔
916

917
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
58✔
918
        ctx.deployment.failed = true;
58✔
919
        ctx.deployment.rollback_failed = true;
58✔
920
}
58✔
921

922
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
923
        ctx.deployment.failed = true;
52✔
924
        ctx.deployment.rollback_failed = false;
52✔
925
}
52✔
926

927
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
928
        ctx.deployment.failed = true;
12✔
929
        ctx.deployment.rollback_failed = true;
12✔
930
}
12✔
931

932
} // namespace deployment_tracking
933

934
} // namespace daemon
935
} // namespace update
936
} // 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