Skip to content

Commit ecc2b16

Browse files
committed
Allow SotaUptaneClient to initialize offline
This finally breaks the dependency on network connectivity and on-line provisioning and allows SotaUptaneClient to start with no network. Part of uptane/aktualizr#8 Signed-off-by: Phil Wise <[email protected]>
1 parent 99692bc commit ecc2b16

File tree

5 files changed

+89
-26
lines changed

5 files changed

+89
-26
lines changed

src/libaktualizr/primary/sotauptaneclient.cc

+31-9
Original file line numberDiff line numberDiff line change
@@ -385,26 +385,29 @@ Json::Value SotaUptaneClient::AssembleManifest() {
385385
bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingInstall(); }
386386

387387
void SotaUptaneClient::initialize() {
388-
bool provisioned = false;
389-
390388
provisioner_.Prepare();
391389

392390
uptane_manifest = std::make_shared<Uptane::ManifestIssuer>(key_manager_, provisioner_.PrimaryEcuSerial());
393391

394392
finalizeAfterReboot();
395-
for (int i = 0; i < 3 && !provisioned; i++) {
396-
provisioned = attemptProvision();
397-
}
398393

399-
// This is temporary. For offline updates it will be necessary to
400-
// run updates before provisioning has completed.
394+
attemptProvision();
395+
}
401396

402-
if (!provisioned) {
403-
throw std::runtime_error("Initialization failed after 3 attempts");
397+
void SotaUptaneClient::requiresProvision() {
398+
if (!attemptProvision()) {
399+
throw std::runtime_error("Device was not able provision on-line");
400+
}
401+
}
402+
403+
void SotaUptaneClient::requiresAlreadyProvisioned() {
404+
if (provisioner_.CurrentState() != Provisioner::State::kOk) {
405+
throw std::runtime_error("Device is not provisioned on-line yet");
404406
}
405407
}
406408

407409
void SotaUptaneClient::updateDirectorMeta() {
410+
requiresProvision();
408411
try {
409412
director_repo.updateMeta(*storage, *uptane_fetcher);
410413
} catch (const std::exception &e) {
@@ -414,6 +417,7 @@ void SotaUptaneClient::updateDirectorMeta() {
414417
}
415418

416419
void SotaUptaneClient::updateImageMeta() {
420+
requiresProvision();
417421
try {
418422
image_repo.updateMeta(*storage, *uptane_fetcher);
419423
} catch (const std::exception &e) {
@@ -423,6 +427,7 @@ void SotaUptaneClient::updateImageMeta() {
423427
}
424428

425429
void SotaUptaneClient::checkDirectorMetaOffline() {
430+
requiresAlreadyProvisioned();
426431
try {
427432
director_repo.checkMetaOffline(*storage);
428433
} catch (const std::exception &e) {
@@ -432,6 +437,7 @@ void SotaUptaneClient::checkDirectorMetaOffline() {
432437
}
433438

434439
void SotaUptaneClient::checkImageMetaOffline() {
440+
requiresAlreadyProvisioned();
435441
try {
436442
image_repo.checkMetaOffline(*storage);
437443
} catch (const std::exception &e) {
@@ -648,6 +654,7 @@ std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(con
648654

649655
result::Download SotaUptaneClient::downloadImages(const std::vector<Uptane::Target> &targets,
650656
const api::FlowControlToken *token) {
657+
requiresAlreadyProvisioned();
651658
// Uptane step 4 - download all the images and verify them against the metadata (for OSTree - pull without
652659
// deploying)
653660
std::lock_guard<std::mutex> guard(download_mutex);
@@ -825,6 +832,8 @@ void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targe
825832
}
826833

827834
void SotaUptaneClient::sendDeviceData() {
835+
requiresProvision();
836+
828837
reportHwInfo();
829838
reportInstalledPackages();
830839
reportNetworkInfo();
@@ -833,6 +842,8 @@ void SotaUptaneClient::sendDeviceData() {
833842
}
834843

835844
result::UpdateCheck SotaUptaneClient::fetchMeta() {
845+
requiresProvision();
846+
836847
result::UpdateCheck result;
837848

838849
reportNetworkInfo();
@@ -974,6 +985,7 @@ result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(const std::vector<Upt
974985
}
975986

976987
result::Install SotaUptaneClient::uptaneInstall(const std::vector<Uptane::Target> &updates) {
988+
requiresAlreadyProvisioned();
977989
const std::string &correlation_id = director_repo.getCorrelationId();
978990

979991
// put most of the logic in a lambda so that we can take care of common
@@ -1090,6 +1102,8 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vector<Uptane::Target
10901102
}
10911103

10921104
result::CampaignCheck SotaUptaneClient::campaignCheck() {
1105+
requiresProvision();
1106+
10931107
auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
10941108
for (const auto &c : campaigns) {
10951109
LOG_INFO << "Campaign: " << c.name;
@@ -1104,16 +1118,22 @@ result::CampaignCheck SotaUptaneClient::campaignCheck() {
11041118
}
11051119

11061120
void SotaUptaneClient::campaignAccept(const std::string &campaign_id) {
1121+
requiresAlreadyProvisioned();
1122+
11071123
sendEvent<event::CampaignAcceptComplete>();
11081124
report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
11091125
}
11101126

11111127
void SotaUptaneClient::campaignDecline(const std::string &campaign_id) {
1128+
requiresAlreadyProvisioned();
1129+
11121130
sendEvent<event::CampaignDeclineComplete>();
11131131
report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
11141132
}
11151133

11161134
void SotaUptaneClient::campaignPostpone(const std::string &campaign_id) {
1135+
requiresAlreadyProvisioned();
1136+
11171137
sendEvent<event::CampaignPostponeComplete>();
11181138
report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
11191139
}
@@ -1169,6 +1189,8 @@ bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) {
11691189
}
11701190

11711191
bool SotaUptaneClient::putManifest(const Json::Value &custom) {
1192+
requiresProvision();
1193+
11721194
bool success = putManifestSimple(custom);
11731195
sendEvent<event::PutManifestComplete>(success);
11741196
return success;

src/libaktualizr/primary/sotauptaneclient.h

+14
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,20 @@ class SotaUptaneClient {
109109
friend class CheckForUpdate; // for load tests
110110
friend class ProvisionDeviceTask; // for load tests
111111

112+
/**
113+
* This operation requires that the device is provisioned.
114+
* Make one attempt at provisioning on-line, and if it fails
115+
* thrown an exception.
116+
*/
117+
void requiresProvision();
118+
119+
/**
120+
* This operation requires that the device is already provisioned.
121+
* If it isn't then fail immediately without attempting any network
122+
* communications.
123+
*/
124+
void requiresAlreadyProvisioned();
125+
112126
data::InstallationResult PackageInstall(const Uptane::Target &target);
113127
std::pair<bool, Uptane::Target> downloadImage(const Uptane::Target &target,
114128
const api::FlowControlToken *token = nullptr);

src/libaktualizr/primary/uptane_key_test.cc

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class UptaneKey_Check_Test {
5555
public:
5656
static void checkKeyTests(std::shared_ptr<INvStorage>& storage, SotaUptaneClient& sota_client) {
5757
EXPECT_NO_THROW(sota_client.initialize());
58+
EXPECT_TRUE(sota_client.attemptProvision());
5859
// Verify that TLS credentials are valid.
5960
std::string ca;
6061
std::string cert;

tests/run_vector_tests.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ while ! curl -I -s -f "http://localhost:$PORT"; do
5959
done
6060

6161
if [[ -n $VALGRIND ]]; then
62-
"$VALGRIND" "$UPTANE_VECTOR_TEST" "$PORT" "$@"
62+
"$VALGRIND" "$UPTANE_VECTOR_TEST" "$PORT" "$TESTS_SRC_DIR" "$@"
6363
else
64-
"$UPTANE_VECTOR_TEST" "$PORT" "$@"
64+
"$UPTANE_VECTOR_TEST" "$PORT" "$TESTS_SRC_DIR" "$@"
6565
fi
6666

6767
RES=$?

tests/uptane_vector_tests.cc

+41-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
#include <gtest/gtest.h>
22

3-
#include <stdio.h>
4-
#include <unistd.h>
5-
#include <cstdlib>
63
#include <iostream>
74
#include <memory>
85
#include <string>
@@ -15,14 +12,17 @@
1512
#include "storage/invstorage.h"
1613
#include "utilities/utils.h"
1714

18-
std::string address;
15+
using std::string;
16+
17+
string address; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
18+
string tests_path; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
1919

2020
class VectorWrapper {
2121
public:
22-
VectorWrapper(Json::Value vector) : vector_(std::move(vector)) {}
22+
explicit VectorWrapper(Json::Value vector) : vector_(std::move(vector)) {}
2323

2424
bool matchError(const Uptane::Exception& e) {
25-
auto me = [this, &e](const std::string r) {
25+
auto me = [this, &e](const string r) {
2626
if (vector_[r]["update"]["err_msg"].asString() == e.what()) {
2727
return true;
2828
}
@@ -81,7 +81,26 @@ class VectorWrapper {
8181
Json::Value vector_;
8282
};
8383

84-
class UptaneVector : public ::testing::TestWithParam<std::string> {};
84+
class UptaneVector : public ::testing::TestWithParam<string> {};
85+
86+
class HttpWrapper : public HttpClient {
87+
public:
88+
HttpResponse post(const string& url, const string& content_type, const string& data) override {
89+
if (url.find("/devices") != string::npos) {
90+
LOG_TRACE << " HttpWrapper intercepting device registration";
91+
return {Utils::readFile(tests_path + "/test_data/cred.p12"), 200, CURLE_OK, ""};
92+
}
93+
94+
if (url.find("/director/ecus") != string::npos) {
95+
LOG_TRACE << " HttpWrapper intercepting Uptane ECU registration";
96+
return {"", 200, CURLE_OK, ""};
97+
}
98+
99+
LOG_TRACE << "HttpWrapper letting " << url << " pass";
100+
return HttpClient::post(url, content_type, data);
101+
}
102+
HttpResponse post(const string& url, const Json::Value& data) override { return HttpClient::post(url, data); }
103+
};
85104

86105
/**
87106
* Check that aktualizr fails on expired metadata.
@@ -90,23 +109,27 @@ class UptaneVector : public ::testing::TestWithParam<std::string> {};
90109
* RecordProperty("zephyr_key", "REQ-153,TST-52");
91110
*/
92111
TEST_P(UptaneVector, Test) {
93-
const std::string test_name = GetParam();
112+
const string test_name = GetParam();
94113
std::cout << "Running test vector " << test_name << "\n";
95114

96115
TemporaryDirectory temp_dir;
97116
Config config;
98117
config.provision.primary_ecu_serial = "test_primary_ecu_serial";
99118
config.provision.primary_ecu_hardware_id = "test_primary_hardware_id";
119+
config.provision.provision_path = tests_path + "/test_data/cred.zip";
120+
config.provision.mode = ProvisionMode::kSharedCredReuse;
100121
config.uptane.director_server = address + test_name + "/director";
101122
config.uptane.repo_server = address + test_name + "/image_repo";
102123
config.storage.path = temp_dir.Path();
103124
config.storage.uptane_metadata_path = utils::BasedPath(temp_dir.Path() / "metadata");
104125
config.pacman.images_path = temp_dir.Path() / "images";
105126
config.pacman.type = PACKAGE_MANAGER_NONE;
127+
config.postUpdateValues();
106128
logger_set_threshold(boost::log::trivial::trace);
107129

108130
auto storage = INvStorage::newStorage(config.storage);
109-
auto uptane_client = std_::make_unique<SotaUptaneClient>(config, storage);
131+
auto http_client = std::make_shared<HttpWrapper>();
132+
auto uptane_client = std_::make_unique<SotaUptaneClient>(config, storage, http_client, nullptr);
110133
auto ecu_serial = uptane_client->provisioner_.PrimaryEcuSerial();
111134
auto hw_id = uptane_client->provisioner_.PrimaryHardwareIdentifier();
112135
EXPECT_EQ(ecu_serial.ToString(), config.provision.primary_ecu_serial);
@@ -115,9 +138,10 @@ TEST_P(UptaneVector, Test) {
115138
Uptane::Target target("test_filename", ecu_map, {{Hash::Type::kSha256, "sha256"}}, 1, "");
116139
storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);
117140

118-
HttpClient http_client;
141+
uptane_client->initialize();
142+
ASSERT_TRUE(uptane_client->attemptProvision()) << "Provisioning Failed. Can't continue test";
119143
while (true) {
120-
HttpResponse response = http_client.post(address + test_name + "/step", Json::Value());
144+
HttpResponse response = http_client->post(address + test_name + "/step", Json::Value());
121145
if (response.http_status_code == 204) {
122146
return;
123147
}
@@ -172,10 +196,10 @@ TEST_P(UptaneVector, Test) {
172196
FAIL() << "Step sequence unexpectedly aborted.";
173197
}
174198

175-
std::vector<std::string> GetVectors() {
199+
std::vector<string> GetVectors() {
176200
HttpClient http_client;
177201
const Json::Value json_vectors = http_client.get(address, HttpInterface::kNoLimit).getJson();
178-
std::vector<std::string> vectors;
202+
std::vector<string> vectors;
179203
for (Json::ValueConstIterator it = json_vectors.begin(); it != json_vectors.end(); it++) {
180204
vectors.emplace_back((*it).asString());
181205
}
@@ -188,17 +212,19 @@ int main(int argc, char* argv[]) {
188212
logger_init();
189213
logger_set_threshold(boost::log::trivial::trace);
190214

191-
if (argc < 2) {
215+
if (argc < 3) {
192216
std::cerr << "This program is intended to be run from run_vector_tests.sh!\n";
193217
return 1;
194218
}
195219

196220
/* Use ports to distinguish both the server connection and local storage so
197221
* that parallel runs of this code don't cause problems that are difficult to
198222
* debug. */
199-
const std::string port = argv[1];
223+
const string port = argv[1];
200224
address = "http://localhost:" + port + "/";
201225

226+
tests_path = argv[2];
227+
202228
::testing::InitGoogleTest(&argc, argv);
203229
return RUN_ALL_TESTS();
204230
}

0 commit comments

Comments
 (0)