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

mendersoftware / mender / 1499142629

16 Oct 2024 06:58PM UTC coverage: 76.305% (-0.06%) from 76.361%
1499142629

push

gitlab-ci

lluiscampos
fix: Invalidate cached inventory

Changelog: Invalidate cached inventory data on unauthentication event
to prevent an issue with which the client would not send inventory
data to the server after being unauthorized and authorized again.

Ticket: MEN-7617

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>

0 of 4 new or added lines in 1 file covered. (0.0%)

268 existing lines in 7 files now uncovered.

7310 of 9580 relevant lines covered (76.3%)

11291.79 hits per line

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

81.77
/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 <client_shared/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::client_shared::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 = [&ctx, &poster](error::Error err) {
114✔
110
                if (err != error::NoError) {
57✔
111
                        log::Error("Failed to submit inventory: " + err.String());
×
112
                        poster.PostEvent(StateEvent::Failure);
×
113
                        return;
×
114
                }
115
                ctx.has_submitted_inventory = true;
57✔
116
                poster.PostEvent(StateEvent::Success);
57✔
117
        };
57✔
118

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

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

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

150
        DoSubmitInventory(ctx, poster);
57✔
151
}
57✔
152

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

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

173
        auto err = ctx.deployment_client->CheckNewDeployments(
174
                ctx.mender_context,
175
                ctx.http_client,
176
                [&ctx, &poster](mender::update::deployments::CheckUpdatesAPIResponse response) {
56✔
177
                        if (!response) {
56✔
178
                                log::Error("Error while polling for deployment: " + response.error().String());
×
179

180
                                // When unauthenticated,
181
                                // invalidate the cached inventory data so that it can be sent again
182
                                // and set clear the context flag so that it is triggered on re-authorization
NEW
183
                                if ((response.error().code == auth::MakeError(auth::UnauthorizedError, "").code)
×
NEW
184
                                        && ctx.has_submitted_inventory) {
×
NEW
185
                                        ctx.inventory_client->ClearDataCache();
×
NEW
186
                                        ctx.has_submitted_inventory = false;
×
187
                                }
UNCOV
188
                                poster.PostEvent(StateEvent::Failure);
×
189
                                return;
1✔
190
                        } else if (!response.value()) {
56✔
191
                                log::Info("No update available");
2✔
192
                                poster.PostEvent(StateEvent::NothingToDo);
1✔
193

194
                                if (not ctx.has_submitted_inventory) {
1✔
195
                                        // If we have not submitted inventory successfully at least
196
                                        // once, schedule this after receiving a successful response
197
                                        // with no update. This enables inventory to be submitted
198
                                        // immediately after the device has been accepted. If there
199
                                        // is an update available, an inventory update will be
200
                                        // scheduled at the end of it unconditionally.
UNCOV
201
                                        poster.PostEvent(StateEvent::InventoryPollingTriggered);
×
202
                                }
203

204
                                return;
1✔
205
                        }
206

207
                        auto exp_data = ApiResponseJsonToStateData(response.value().value());
55✔
208
                        if (!exp_data) {
55✔
UNCOV
209
                                log::Error("Error in API response: " + exp_data.error().String());
×
210
                                poster.PostEvent(StateEvent::Failure);
×
211
                                return;
212
                        }
213

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

217
                        ctx.BeginDeploymentLogging();
55✔
218

219
                        log::Info("Running Mender client " + conf::kMenderVersion);
110✔
220
                        log::Info(
55✔
221
                                "Deployment with ID " + ctx.deployment.state_data->update_info.id + " started.");
110✔
222

223
                        poster.PostEvent(StateEvent::DeploymentStarted);
55✔
224
                        poster.PostEvent(StateEvent::Success);
55✔
225
                });
57✔
226

227
        if (err != error::NoError) {
57✔
228
                log::Error("Error when trying to poll for deployment: " + err.String());
2✔
229
                poster.PostEvent(StateEvent::Failure);
1✔
230
        }
231
}
57✔
232

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

236
        ctx.deployment.state_data->state = DatabaseStateString();
534✔
237

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

240
        auto err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
534✔
241
        if (err != error::NoError) {
534✔
242
                log::Error(err.String());
10✔
243
                if (err.code
10✔
244
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
10✔
245
                        poster.PostEvent(StateEvent::StateLoopDetected);
1✔
246
                        return;
247
                } else if (!IsFailureState()) {
9✔
248
                        // Non-failure states should be interrupted, but failure states should be
249
                        // allowed to do their work, even if a database error was detected.
250
                        poster.PostEvent(StateEvent::Failure);
2✔
251
                        return;
252
                }
253
        }
254

255
        OnEnterSaveState(ctx, poster);
531✔
256
}
257

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

261
        auto req = make_shared<http::OutgoingRequest>();
53✔
262
        req->SetMethod(http::Method::GET);
53✔
263
        auto err = req->SetAddress(ctx.deployment.state_data->update_info.artifact.source.uri);
53✔
264
        if (err != error::NoError) {
53✔
UNCOV
265
                log::Error(err.String());
×
UNCOV
266
                poster.PostEvent(StateEvent::Failure);
×
267
                return;
268
        }
269

270
        err = ctx.download_client->AsyncCall(
53✔
271
                req,
272
                [&ctx, &poster](http::ExpectedIncomingResponsePtr exp_resp) {
105✔
273
                        if (!exp_resp) {
53✔
274
                                log::Error("Unexpected error during download: " + exp_resp.error().String());
×
275
                                poster.PostEvent(StateEvent::Failure);
×
276
                                return;
1✔
277
                        }
278

279
                        auto &resp = exp_resp.value();
53✔
280
                        if (resp->GetStatusCode() != http::StatusOK) {
53✔
281
                                log::Error(
1✔
282
                                        "Unexpected status code while fetching artifact: " + resp->GetStatusMessage());
2✔
283
                                ctx.download_client->Cancel();
1✔
284
                                poster.PostEvent(StateEvent::Failure);
1✔
285
                                return;
1✔
286
                        }
287

288
                        auto http_reader = resp->MakeBodyAsyncReader();
52✔
289
                        if (!http_reader) {
52✔
UNCOV
290
                                log::Error(http_reader.error().String());
×
UNCOV
291
                                ctx.download_client->Cancel();
×
UNCOV
292
                                poster.PostEvent(StateEvent::Failure);
×
293
                                return;
294
                        }
295
                        ctx.deployment.artifact_reader =
296
                                make_shared<events::io::ReaderFromAsyncReader>(ctx.event_loop, http_reader.value());
52✔
297
                        ParseArtifact(ctx, poster);
52✔
298
                },
299
                [](http::ExpectedIncomingResponsePtr exp_resp) {
53✔
300
                        if (!exp_resp) {
53✔
301
                                log::Error(exp_resp.error().String());
7✔
302
                                // Cannot handle error here, because this handler is called at the
303
                                // end of the download, when we have already left this state. So
304
                                // rely on this error being propagated through the BodyAsyncReader
305
                                // above instead.
306
                                return;
7✔
307
                        }
308
                });
106✔
309

310
        if (err != error::NoError) {
53✔
UNCOV
311
                log::Error(err.String());
×
UNCOV
312
                poster.PostEvent(StateEvent::Failure);
×
313
                return;
314
        }
315
}
316

317
void UpdateDownloadState::ParseArtifact(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
318
        string art_scripts_path = ctx.mender_context.GetConfig().paths.GetArtScriptsPath();
52✔
319

320
        // Clear the artifact scripts directory so we don't risk old scripts lingering.
321
        auto err = path::DeleteRecursively(art_scripts_path);
52✔
322
        if (err != error::NoError) {
52✔
UNCOV
323
                log::Error("When preparing to parse artifact: " + err.String());
×
UNCOV
324
                poster.PostEvent(StateEvent::Failure);
×
325
                return;
326
        }
327

328
        artifact::config::ParserConfig config {
52✔
329
                .artifact_scripts_filesystem_path = art_scripts_path,
330
                .artifact_scripts_version = 3,
331
                .artifact_verify_keys = ctx.mender_context.GetConfig().artifact_verify_keys,
52✔
332
        };
100✔
333
        auto exp_parser = artifact::Parse(*ctx.deployment.artifact_reader, config);
104✔
334
        if (!exp_parser) {
52✔
UNCOV
335
                log::Error(exp_parser.error().String());
×
UNCOV
336
                poster.PostEvent(StateEvent::Failure);
×
337
                return;
338
        }
339
        ctx.deployment.artifact_parser.reset(new artifact::Artifact(std::move(exp_parser.value())));
52✔
340

341
        auto exp_header = artifact::View(*ctx.deployment.artifact_parser, 0);
52✔
342
        if (!exp_header) {
52✔
UNCOV
343
                log::Error(exp_header.error().String());
×
344
                poster.PostEvent(StateEvent::Failure);
×
345
                return;
346
        }
347
        auto &header = exp_header.value();
52✔
348

349
        auto exp_matches = ctx.mender_context.MatchesArtifactDepends(header.header);
52✔
350
        if (!exp_matches) {
52✔
351
                log::Error(exp_matches.error().String());
2✔
352
                poster.PostEvent(StateEvent::Failure);
2✔
353
                return;
354
        } else if (!exp_matches.value()) {
50✔
355
                // reasons already logged
356
                poster.PostEvent(StateEvent::Failure);
1✔
357
                return;
358
        }
359

360
        log::Info("Installing artifact...");
98✔
361

362
        ctx.deployment.state_data->FillUpdateDataFromArtifact(header);
49✔
363

364
        ctx.deployment.state_data->state = Context::kUpdateStateDownload;
49✔
365

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

368
        // Initial state data save, now that we have enough information from the artifact.
369
        err = ctx.SaveDeploymentStateData(*ctx.deployment.state_data);
49✔
370
        if (err != error::NoError) {
49✔
UNCOV
371
                log::Error(err.String());
×
UNCOV
372
                if (err.code
×
UNCOV
373
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
UNCOV
374
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
375
                        return;
376
                } else {
UNCOV
377
                        poster.PostEvent(StateEvent::Failure);
×
378
                        return;
379
                }
380
        }
381

382
        if (header.header.payload_type == "") {
49✔
383
                // Empty-payload-artifact, aka "bootstrap artifact".
384
                poster.PostEvent(StateEvent::NothingToDo);
1✔
385
                return;
386
        }
387

388
        ctx.deployment.update_module.reset(
389
                new update_module::UpdateModule(ctx.mender_context, header.header.payload_type));
48✔
390

391
        err = ctx.deployment.update_module->CleanAndPrepareFileTree(
48✔
392
                ctx.deployment.update_module->GetUpdateModuleWorkDir(), header);
48✔
393
        if (err != error::NoError) {
48✔
UNCOV
394
                log::Error(err.String());
×
UNCOV
395
                poster.PostEvent(StateEvent::Failure);
×
396
                return;
397
        }
398

399
        err = ctx.deployment.update_module->AsyncProvidePayloadFileSizes(
48✔
400
                ctx.event_loop, [&ctx, &poster](expected::ExpectedBool download_with_sizes) {
48✔
401
                        if (!download_with_sizes.has_value()) {
48✔
UNCOV
402
                                log::Error(download_with_sizes.error().String());
×
403
                                poster.PostEvent(StateEvent::Failure);
×
404
                                return;
×
405
                        }
406
                        ctx.deployment.download_with_sizes = download_with_sizes.value();
48✔
407
                        DoDownload(ctx, poster);
48✔
408
                });
48✔
409

410
        if (err != error::NoError) {
48✔
411
                log::Error(err.String());
×
412
                poster.PostEvent(StateEvent::Failure);
×
413
                return;
414
        }
415
}
416

417
void UpdateDownloadState::DoDownload(Context &ctx, sm::EventPoster<StateEvent> &poster) {
48✔
418
        auto exp_payload = ctx.deployment.artifact_parser->Next();
48✔
419
        if (!exp_payload) {
48✔
420
                log::Error(exp_payload.error().String());
×
421
                poster.PostEvent(StateEvent::Failure);
×
422
                return;
423
        }
424
        ctx.deployment.artifact_payload.reset(new artifact::Payload(std::move(exp_payload.value())));
48✔
425

426
        auto handler = [&poster, &ctx](error::Error err) {
46✔
427
                if (err != error::NoError) {
48✔
428
                        log::Error(err.String());
2✔
429
                        poster.PostEvent(StateEvent::Failure);
2✔
430
                        return;
2✔
431
                }
432

433
                auto exp_payload = ctx.deployment.artifact_parser->Next();
46✔
434
                if (exp_payload) {
46✔
UNCOV
435
                        log::Error("Multiple payloads are not yet supported in daemon mode.");
×
UNCOV
436
                        poster.PostEvent(StateEvent::Failure);
×
437
                        return;
438
                } else if (
46✔
439
                        exp_payload.error().code
440
                        != artifact::parser_error::MakeError(artifact::parser_error::EOFError, "").code) {
46✔
UNCOV
441
                        log::Error(exp_payload.error().String());
×
UNCOV
442
                        poster.PostEvent(StateEvent::Failure);
×
443
                        return;
444
                }
445

446
                poster.PostEvent(StateEvent::Success);
46✔
447
        };
448

449
        if (ctx.deployment.download_with_sizes) {
48✔
450
                ctx.deployment.update_module->AsyncDownloadWithFileSizes(
1✔
451
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
1✔
452
        } else {
453
                ctx.deployment.update_module->AsyncDownload(
47✔
454
                        ctx.event_loop, *ctx.deployment.artifact_payload, handler);
47✔
455
        }
456
}
457

UNCOV
458
SendStatusUpdateState::SendStatusUpdateState(optional<deployments::DeploymentStatus> status) :
×
459
        status_(status),
UNCOV
460
        mode_(FailureMode::Ignore) {
×
UNCOV
461
}
×
462

463
SendStatusUpdateState::SendStatusUpdateState(
188✔
464
        optional<deployments::DeploymentStatus> status,
465
        events::EventLoop &event_loop,
466
        int retry_interval_seconds,
467
        int retry_count) :
468
        status_(status),
469
        mode_(FailureMode::RetryThenFail),
470
        retry_(Retry {
188✔
471
                http::ExponentialBackoff(chrono::seconds(retry_interval_seconds), retry_count),
472
                event_loop}) {
564✔
473
}
188✔
474

475
void SendStatusUpdateState::SetSmallestWaitInterval(chrono::milliseconds interval) {
178✔
476
        if (retry_) {
178✔
477
                retry_->backoff.SetSmallestInterval(interval);
178✔
478
        }
479
}
178✔
480

481
void SendStatusUpdateState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
242✔
482
        // Reset this every time we enter the state, which means a new round of retries.
483
        if (retry_) {
242✔
484
                retry_->backoff.Reset();
485
        }
486

487
        DoStatusUpdate(ctx, poster);
242✔
488
}
242✔
489

490
void SendStatusUpdateState::DoStatusUpdate(Context &ctx, sm::EventPoster<StateEvent> &poster) {
261✔
491
        assert(ctx.deployment_client);
492
        assert(ctx.deployment.state_data);
493

494
        log::Info("Sending status update to server");
522✔
495

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

500
                        switch (mode_) {
24✔
501
                        case FailureMode::Ignore:
502
                                break;
3✔
503
                        case FailureMode::RetryThenFail:
504
                                if (err.code
21✔
505
                                        == deployments::MakeError(deployments::DeploymentAbortedError, "").code) {
21✔
506
                                        // If the deployment was aborted upstream it is an immediate
507
                                        // failure, even if retry is enabled.
508
                                        poster.PostEvent(StateEvent::Failure);
1✔
509
                                        return;
21✔
510
                                }
511

512
                                auto exp_interval = retry_->backoff.NextInterval();
20✔
513
                                if (!exp_interval) {
20✔
514
                                        log::Error(
1✔
515
                                                "Giving up on sending status updates to server: "
516
                                                + exp_interval.error().String());
2✔
517
                                        poster.PostEvent(StateEvent::Failure);
1✔
518
                                        return;
519
                                }
520

521
                                log::Info(
19✔
522
                                        "Retrying status update after "
523
                                        + to_string(chrono::duration_cast<chrono::seconds>(*exp_interval).count())
38✔
524
                                        + " seconds");
38✔
525
                                retry_->wait_timer.AsyncWait(
19✔
526
                                        *exp_interval, [this, &ctx, &poster](error::Error err) {
38✔
527
                                                // Error here is quite unexpected (from a timer), so treat
528
                                                // this as an immediate error, despite Retry flag.
529
                                                if (err != error::NoError) {
19✔
UNCOV
530
                                                        log::Error(
×
531
                                                                "Unexpected error in SendStatusUpdateState wait timer: "
UNCOV
532
                                                                + err.String());
×
UNCOV
533
                                                        poster.PostEvent(StateEvent::Failure);
×
UNCOV
534
                                                        return;
×
535
                                                }
536

537
                                                // Try again. Since both status and logs are sent
538
                                                // from here, there's a chance this might resubmit
539
                                                // the status, but there's no harm in it, and it
540
                                                // won't happen often.
541
                                                DoStatusUpdate(ctx, poster);
19✔
542
                                        });
19✔
543
                                return;
19✔
544
                        }
545
                }
546

547
                poster.PostEvent(StateEvent::Success);
240✔
548
        };
261✔
549

550
        deployments::DeploymentStatus status;
551
        if (status_) {
261✔
552
                status = status_.value();
170✔
553
        } else {
554
                // If nothing is specified, grab success/failure status from the deployment status.
555
                if (ctx.deployment.failed) {
91✔
556
                        status = deployments::DeploymentStatus::Failure;
557
                } else {
558
                        status = deployments::DeploymentStatus::Success;
559
                }
560
        }
561

562
        // Push status.
563
        log::Debug("Pushing deployment status: " + DeploymentStatusString(status));
522✔
564
        auto err = ctx.deployment_client->PushStatus(
565
                ctx.deployment.state_data->update_info.id,
261✔
566
                status,
567
                "",
568
                ctx.http_client,
569
                [result_handler, &ctx](error::Error err) {
73✔
570
                        // If there is an error, we don't submit logs now, but call the handler,
571
                        // which may schedule a retry later. If there is no error, and the
572
                        // deployment as a whole was successful, then also call the handler here,
573
                        // since we don't need to submit logs at all then.
574
                        if (err != error::NoError || !ctx.deployment.failed) {
261✔
575
                                result_handler(err);
188✔
576
                                return;
188✔
577
                        }
578

579
                        // Push logs.
580
                        err = ctx.deployment_client->PushLogs(
73✔
581
                                ctx.deployment.state_data->update_info.id,
73✔
582
                                ctx.deployment.logger->LogFilePath(),
146✔
583
                                ctx.http_client,
584
                                result_handler);
73✔
585

586
                        if (err != error::NoError) {
73✔
UNCOV
587
                                result_handler(err);
×
588
                        }
589
                });
522✔
590

591
        if (err != error::NoError) {
261✔
UNCOV
592
                result_handler(err);
×
593
        }
594

595
        // No action, wait for reply from status endpoint.
596
}
261✔
597

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

601
        DefaultAsyncErrorHandler(
42✔
602
                poster,
603
                ctx.deployment.update_module->AsyncArtifactInstall(
42✔
604
                        ctx.event_loop, DefaultStateHandler {poster}));
42✔
605
}
42✔
606

607
void UpdateCheckRebootState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
73✔
608
        DefaultAsyncErrorHandler(
73✔
609
                poster,
610
                ctx.deployment.update_module->AsyncNeedsReboot(
73✔
611
                        ctx.event_loop, [&ctx, &poster](update_module::ExpectedRebootAction reboot_action) {
144✔
612
                                if (!reboot_action.has_value()) {
73✔
613
                                        log::Error(reboot_action.error().String());
2✔
614
                                        poster.PostEvent(StateEvent::Failure);
2✔
615
                                        return;
2✔
616
                                }
617

618
                                ctx.deployment.state_data->update_info.reboot_requested.resize(1);
71✔
619
                                ctx.deployment.state_data->update_info.reboot_requested[0] =
620
                                        NeedsRebootToDbString(*reboot_action);
71✔
621
                                switch (*reboot_action) {
71✔
622
                                case update_module::RebootAction::No:
8✔
623
                                        poster.PostEvent(StateEvent::NothingToDo);
8✔
624
                                        break;
8✔
625
                                case update_module::RebootAction::Yes:
63✔
626
                                case update_module::RebootAction::Automatic:
627
                                        poster.PostEvent(StateEvent::Success);
63✔
628
                                        break;
63✔
629
                                }
630
                        }));
73✔
631
}
73✔
632

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

636
        assert(ctx.deployment.state_data->update_info.reboot_requested.size() == 1);
637
        auto exp_reboot_mode =
638
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
27✔
639
        // Should always be true because we check it at load time.
640
        assert(exp_reboot_mode);
641

642
        switch (exp_reboot_mode.value()) {
27✔
UNCOV
643
        case update_module::RebootAction::No:
×
644
                // Should not happen because then we don't enter this state.
645
                assert(false);
UNCOV
646
                poster.PostEvent(StateEvent::Failure);
×
647
                break;
648
        case update_module::RebootAction::Yes:
27✔
649
                DefaultAsyncErrorHandler(
27✔
650
                        poster,
651
                        ctx.deployment.update_module->AsyncArtifactReboot(
27✔
652
                                ctx.event_loop, DefaultStateHandler {poster}));
27✔
653
                break;
27✔
UNCOV
654
        case update_module::RebootAction::Automatic:
×
655
                DefaultAsyncErrorHandler(
×
656
                        poster,
UNCOV
657
                        ctx.deployment.update_module->AsyncSystemReboot(
×
UNCOV
658
                                ctx.event_loop, DefaultStateHandler {poster}));
×
UNCOV
659
                break;
×
660
        }
661
}
27✔
662

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

666
        ctx.deployment.update_module->EnsureRootfsImageFileTree(
30✔
667
                ctx.deployment.update_module->GetUpdateModuleWorkDir());
60✔
668

669
        DefaultAsyncErrorHandler(
30✔
670
                poster,
671
                ctx.deployment.update_module->AsyncArtifactVerifyReboot(
30✔
672
                        ctx.event_loop, DefaultStateHandler {poster}));
30✔
673
}
30✔
674

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

680
        poster.PostEvent(StateEvent::Success);
23✔
681
}
23✔
682

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

686
        // Explicitly check if state scripts version is supported
687
        auto err = script_executor::CheckScriptsCompatibility(
688
                ctx.mender_context.GetConfig().paths.GetRootfsScriptsPath());
19✔
689
        if (err != error::NoError) {
19✔
UNCOV
690
                log::Error("Failed script compatibility check: " + err.String());
×
UNCOV
691
                poster.PostEvent(StateEvent::Failure);
×
692
                return;
693
        }
694

695
        DefaultAsyncErrorHandler(
19✔
696
                poster,
697
                ctx.deployment.update_module->AsyncArtifactCommit(
19✔
698
                        ctx.event_loop, DefaultStateHandler {poster}));
38✔
699
}
700

701
void UpdateAfterCommitState::OnEnterSaveState(Context &ctx, sm::EventPoster<StateEvent> &poster) {
19✔
702
        // Now we have committed. If we had a schema update, re-save state data with the new schema.
703
        assert(ctx.deployment.state_data);
704
        auto &state_data = *ctx.deployment.state_data;
705
        if (state_data.update_info.has_db_schema_update) {
19✔
UNCOV
706
                state_data.update_info.has_db_schema_update = false;
×
UNCOV
707
                auto err = ctx.SaveDeploymentStateData(state_data);
×
UNCOV
708
                if (err != error::NoError) {
×
UNCOV
709
                        log::Error("Not able to commit schema update: " + err.String());
×
UNCOV
710
                        poster.PostEvent(StateEvent::Failure);
×
711
                        return;
712
                }
713
        }
714

715
        poster.PostEvent(StateEvent::Success);
19✔
716
}
717

718
void UpdateCheckRollbackState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
45✔
719
        DefaultAsyncErrorHandler(
45✔
720
                poster,
721
                ctx.deployment.update_module->AsyncSupportsRollback(
45✔
722
                        ctx.event_loop, [&ctx, &poster](expected::ExpectedBool rollback_supported) {
89✔
723
                                if (!rollback_supported.has_value()) {
45✔
724
                                        log::Error(rollback_supported.error().String());
1✔
725
                                        poster.PostEvent(StateEvent::Failure);
1✔
726
                                        return;
1✔
727
                                }
728

729
                                ctx.deployment.state_data->update_info.supports_rollback =
730
                                        SupportsRollbackToDbString(*rollback_supported);
44✔
731
                                if (*rollback_supported) {
44✔
732
                                        poster.PostEvent(StateEvent::RollbackStarted);
38✔
733
                                        poster.PostEvent(StateEvent::Success);
38✔
734
                                } else {
735
                                        poster.PostEvent(StateEvent::NothingToDo);
6✔
736
                                }
737
                        }));
45✔
738
}
45✔
739

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

743
        DefaultAsyncErrorHandler(
41✔
744
                poster,
745
                ctx.deployment.update_module->AsyncArtifactRollback(
41✔
746
                        ctx.event_loop, DefaultStateHandler {poster}));
41✔
747
}
41✔
748

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

752
        auto exp_reboot_mode =
753
                DbStringToNeedsReboot(ctx.deployment.state_data->update_info.reboot_requested[0]);
57✔
754
        // Should always be true because we check it at load time.
755
        assert(exp_reboot_mode);
756

757
        // We ignore errors in this state as long as the ArtifactVerifyRollbackReboot state
758
        // succeeds.
759
        auto handler = [&poster](error::Error err) {
114✔
760
                if (err != error::NoError) {
57✔
761
                        log::Error(err.String());
2✔
762
                }
763
                poster.PostEvent(StateEvent::Success);
57✔
764
        };
57✔
765

766
        error::Error err;
57✔
767
        switch (exp_reboot_mode.value()) {
57✔
768
        case update_module::RebootAction::No:
769
                // Should not happen because then we don't enter this state.
770
                assert(false);
771

UNCOV
772
                err = error::MakeError(
×
UNCOV
773
                        error::ProgrammingError, "Entered UpdateRollbackRebootState with RebootAction = No");
×
UNCOV
774
                break;
×
775

776
        case update_module::RebootAction::Yes:
57✔
777
                err = ctx.deployment.update_module->AsyncArtifactRollbackReboot(ctx.event_loop, handler);
114✔
778
                break;
57✔
779

UNCOV
780
        case update_module::RebootAction::Automatic:
×
781
                err = ctx.deployment.update_module->AsyncSystemReboot(ctx.event_loop, handler);
×
782
                break;
×
783
        }
784

785
        if (err != error::NoError) {
57✔
UNCOV
786
                log::Error(err.String());
×
UNCOV
787
                poster.PostEvent(StateEvent::Success);
×
788
        }
789
}
57✔
790

791
void UpdateVerifyRollbackRebootState::OnEnterSaveState(
60✔
792
        Context &ctx, sm::EventPoster<StateEvent> &poster) {
793
        log::Debug("Entering ArtifactVerifyRollbackReboot state");
120✔
794

795
        // In this state we only retry, we don't fail. If this keeps on going forever, then the
796
        // state loop detection will eventually kick in.
797
        auto err = ctx.deployment.update_module->AsyncArtifactVerifyRollbackReboot(
798
                ctx.event_loop, [&poster](error::Error err) {
120✔
799
                        if (err != error::NoError) {
60✔
800
                                log::Error(err.String());
22✔
801
                                poster.PostEvent(StateEvent::Retry);
22✔
802
                                return;
22✔
803
                        }
804
                        poster.PostEvent(StateEvent::Success);
38✔
805
                });
60✔
806
        if (err != error::NoError) {
60✔
UNCOV
807
                log::Error(err.String());
×
UNCOV
808
                poster.PostEvent(StateEvent::Retry);
×
809
        }
810
}
60✔
811

812
void UpdateRollbackSuccessfulState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
50✔
813
        ctx.deployment.state_data->update_info.all_rollbacks_successful = true;
50✔
814
        poster.PostEvent(StateEvent::Success);
50✔
815
}
50✔
816

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

820
        DefaultAsyncErrorHandler(
55✔
821
                poster,
822
                ctx.deployment.update_module->AsyncArtifactFailure(
55✔
823
                        ctx.event_loop, DefaultStateHandler {poster}));
55✔
824
}
55✔
825

826
static string AddInconsistentSuffix(const string &str) {
21✔
827
        const auto &suffix = main_context::MenderContext::broken_artifact_name_suffix;
828
        // `string::ends_with` is C++20... grumble
829
        string ret {str};
21✔
830
        if (!common::EndsWith(ret, suffix)) {
21✔
831
                ret.append(suffix);
21✔
832
        }
833
        return ret;
21✔
834
}
835

836
void UpdateSaveProvidesState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
75✔
837
        if (ctx.deployment.failed && !ctx.deployment.rollback_failed) {
75✔
838
                // If the update failed, but we rolled back successfully, then we don't need to do
839
                // anything, just keep the old data.
840
                poster.PostEvent(StateEvent::Success);
38✔
841
                return;
38✔
842
        }
843

844
        assert(ctx.deployment.state_data);
845
        // This state should never happen: rollback failed, but update not failed??
846
        assert(!(!ctx.deployment.failed && ctx.deployment.rollback_failed));
847

848
        // We expect Cleanup to be the next state after this.
849
        ctx.deployment.state_data->state = ctx.kUpdateStateCleanup;
37✔
850

851
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
852

853
        string artifact_name;
854
        if (ctx.deployment.rollback_failed) {
37✔
855
                artifact_name = AddInconsistentSuffix(artifact.artifact_name);
38✔
856
        } else {
857
                artifact_name = artifact.artifact_name;
18✔
858
        }
859

860
        bool deploy_failed = ctx.deployment.failed;
37✔
861

862
        // Only the artifact_name and group should be committed in the case of a
863
        // failing update in order to make this consistent with the old client
864
        // behaviour.
865
        auto err = ctx.mender_context.CommitArtifactData(
37✔
866
                artifact_name,
867
                artifact.artifact_group,
37✔
868
                deploy_failed ? nullopt : optional<context::ProvidesData>(artifact.type_info_provides),
74✔
869
                /* Special case: Keep existing provides */
870
                deploy_failed ? context::ClearsProvidesData {}
93✔
871
                                          : optional<context::ClearsProvidesData>(artifact.clears_artifact_provides),
18✔
872
                [&ctx](kv_db::Transaction &txn) {
37✔
873
                        // Save the Cleanup state together with the artifact data, atomically.
874
                        return ctx.SaveDeploymentStateData(txn, *ctx.deployment.state_data);
37✔
875
                });
74✔
876
        if (err != error::NoError) {
37✔
UNCOV
877
                log::Error("Error saving artifact data: " + err.String());
×
UNCOV
878
                if (err.code
×
UNCOV
879
                        == main_context::MakeError(main_context::StateDataStoreCountExceededError, "").code) {
×
UNCOV
880
                        poster.PostEvent(StateEvent::StateLoopDetected);
×
881
                        return;
882
                }
UNCOV
883
                poster.PostEvent(StateEvent::Failure);
×
884
                return;
885
        }
886

887
        poster.PostEvent(StateEvent::Success);
37✔
888
}
889

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

893
        // It's possible for there not to be an initialized update_module structure, if the
894
        // deployment failed before we could successfully parse the artifact. If so, cleanup is a
895
        // no-op.
896
        if (!ctx.deployment.update_module) {
89✔
897
                poster.PostEvent(StateEvent::Success);
8✔
898
                return;
8✔
899
        }
900

901
        DefaultAsyncErrorHandler(
81✔
902
                poster,
903
                ctx.deployment.update_module->AsyncCleanup(ctx.event_loop, DefaultStateHandler {poster}));
162✔
904
}
905

906
void ClearArtifactDataState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
907
        auto err = ctx.mender_context.GetMenderStoreDB().WriteTransaction([](kv_db::Transaction &txn) {
91✔
908
                // Remove state data, since we're done now.
909
                auto err = txn.Remove(main_context::MenderContext::state_data_key);
89✔
910
                if (err != error::NoError) {
89✔
UNCOV
911
                        return err;
×
912
                }
913
                return txn.Remove(main_context::MenderContext::state_data_key_uncommitted);
89✔
914
        });
91✔
915
        if (err != error::NoError) {
91✔
916
                log::Error("Error removing artifact data: " + err.String());
4✔
917
                poster.PostEvent(StateEvent::Failure);
2✔
918
                return;
919
        }
920

921
        poster.PostEvent(StateEvent::Success);
89✔
922
}
923

924
void StateLoopState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
2✔
925
        assert(ctx.deployment.state_data);
926
        auto &artifact = ctx.deployment.state_data->update_info.artifact;
927

928
        // Mark update as inconsistent.
929
        string artifact_name = AddInconsistentSuffix(artifact.artifact_name);
2✔
930

931
        auto err = ctx.mender_context.CommitArtifactData(
2✔
932
                artifact_name,
933
                artifact.artifact_group,
2✔
934
                artifact.type_info_provides,
2✔
935
                artifact.clears_artifact_provides,
2✔
936
                [](kv_db::Transaction &txn) { return error::NoError; });
6✔
937
        if (err != error::NoError) {
2✔
UNCOV
938
                log::Error("Error saving inconsistent artifact data: " + err.String());
×
UNCOV
939
                poster.PostEvent(StateEvent::Failure);
×
940
                return;
941
        }
942

943
        poster.PostEvent(StateEvent::Success);
2✔
944
}
945

946
void EndOfDeploymentState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
947
        log::Info(
91✔
948
                "Deployment with ID " + ctx.deployment.state_data->update_info.id
182✔
949
                + " finished with status: " + string(ctx.deployment.failed ? "Failure" : "Success"));
382✔
950

951
        ctx.FinishDeploymentLogging();
91✔
952

953
        ctx.deployment = {};
91✔
954
        poster.PostEvent(
91✔
955
                StateEvent::InventoryPollingTriggered); // Submit the inventory right after an update
91✔
956
        poster.PostEvent(StateEvent::DeploymentEnded);
91✔
957
        poster.PostEvent(StateEvent::Success);
91✔
958
}
91✔
959

960
ExitState::ExitState(events::EventLoop &event_loop) :
94✔
961
        event_loop_(event_loop) {
188✔
962
}
94✔
963

964
void ExitState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
91✔
965
#ifndef NDEBUG
966
        if (--iterations_left_ <= 0) {
967
                event_loop_.Stop();
968
        } else {
969
                poster.PostEvent(StateEvent::Success);
970
        }
971
#else
972
        event_loop_.Stop();
91✔
973
#endif
974
}
91✔
975

976
namespace deployment_tracking {
977

978
void NoFailuresState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
61✔
979
        ctx.deployment.failed = false;
61✔
980
        ctx.deployment.rollback_failed = false;
61✔
981
}
61✔
982

983
void FailureState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
58✔
984
        ctx.deployment.failed = true;
58✔
985
        ctx.deployment.rollback_failed = true;
58✔
986
}
58✔
987

988
void RollbackAttemptedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
52✔
989
        ctx.deployment.failed = true;
52✔
990
        ctx.deployment.rollback_failed = false;
52✔
991
}
52✔
992

993
void RollbackFailedState::OnEnter(Context &ctx, sm::EventPoster<StateEvent> &poster) {
12✔
994
        ctx.deployment.failed = true;
12✔
995
        ctx.deployment.rollback_failed = true;
12✔
996
}
12✔
997

998
} // namespace deployment_tracking
999

1000
} // namespace daemon
1001
} // namespace update
1002
} // 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