Skip to content

Commit

Permalink
Allow SotaUptaneClient to initialize offline
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
cajun-rat committed Jan 24, 2022
1 parent 99692bc commit 67c8555
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 23 deletions.
40 changes: 31 additions & 9 deletions src/libaktualizr/primary/sotauptaneclient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -385,26 +385,29 @@ Json::Value SotaUptaneClient::AssembleManifest() {
bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingInstall(); }

void SotaUptaneClient::initialize() {
bool provisioned = false;

provisioner_.Prepare();

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

finalizeAfterReboot();
for (int i = 0; i < 3 && !provisioned; i++) {
provisioned = attemptProvision();
}

// This is temporary. For offline updates it will be necessary to
// run updates before provisioning has completed.
attemptProvision();
}

if (!provisioned) {
throw std::runtime_error("Initialization failed after 3 attempts");
void SotaUptaneClient::requiresProvision() {
if (!attemptProvision()) {
throw std::runtime_error("Device was not able provision on-line");
}
}

void SotaUptaneClient::requiresAlreadyProvisioned() {
if (provisioner_.CurrentState() != Provisioner::State::kOk) {
throw std::runtime_error("Device is not provisioned on-line yet");
}
}

void SotaUptaneClient::updateDirectorMeta() {
requiresProvision();
try {
director_repo.updateMeta(*storage, *uptane_fetcher);
} catch (const std::exception &e) {
Expand All @@ -414,6 +417,7 @@ void SotaUptaneClient::updateDirectorMeta() {
}

void SotaUptaneClient::updateImageMeta() {
requiresProvision();
try {
image_repo.updateMeta(*storage, *uptane_fetcher);
} catch (const std::exception &e) {
Expand All @@ -423,6 +427,7 @@ void SotaUptaneClient::updateImageMeta() {
}

void SotaUptaneClient::checkDirectorMetaOffline() {
requiresAlreadyProvisioned();
try {
director_repo.checkMetaOffline(*storage);
} catch (const std::exception &e) {
Expand All @@ -432,6 +437,7 @@ void SotaUptaneClient::checkDirectorMetaOffline() {
}

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

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

void SotaUptaneClient::sendDeviceData() {
requiresProvision();

reportHwInfo();
reportInstalledPackages();
reportNetworkInfo();
Expand All @@ -833,6 +842,8 @@ void SotaUptaneClient::sendDeviceData() {
}

result::UpdateCheck SotaUptaneClient::fetchMeta() {
requiresProvision();

result::UpdateCheck result;

reportNetworkInfo();
Expand Down Expand Up @@ -974,6 +985,7 @@ result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(const std::vector<Upt
}

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

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

result::CampaignCheck SotaUptaneClient::campaignCheck() {
requiresProvision();

auto campaigns = campaign::Campaign::fetchAvailableCampaigns(*http, config.tls.server);
for (const auto &c : campaigns) {
LOG_INFO << "Campaign: " << c.name;
Expand All @@ -1104,16 +1118,22 @@ result::CampaignCheck SotaUptaneClient::campaignCheck() {
}

void SotaUptaneClient::campaignAccept(const std::string &campaign_id) {
requiresAlreadyProvisioned();

sendEvent<event::CampaignAcceptComplete>();
report_queue->enqueue(std_::make_unique<CampaignAcceptedReport>(campaign_id));
}

void SotaUptaneClient::campaignDecline(const std::string &campaign_id) {
requiresAlreadyProvisioned();

sendEvent<event::CampaignDeclineComplete>();
report_queue->enqueue(std_::make_unique<CampaignDeclinedReport>(campaign_id));
}

void SotaUptaneClient::campaignPostpone(const std::string &campaign_id) {
requiresAlreadyProvisioned();

sendEvent<event::CampaignPostponeComplete>();
report_queue->enqueue(std_::make_unique<CampaignPostponedReport>(campaign_id));
}
Expand Down Expand Up @@ -1169,6 +1189,8 @@ bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) {
}

bool SotaUptaneClient::putManifest(const Json::Value &custom) {
requiresProvision();

bool success = putManifestSimple(custom);
sendEvent<event::PutManifestComplete>(success);
return success;
Expand Down
14 changes: 14 additions & 0 deletions src/libaktualizr/primary/sotauptaneclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,20 @@ class SotaUptaneClient {
friend class CheckForUpdate; // for load tests
friend class ProvisionDeviceTask; // for load tests

/**
* This operation requires that the device is provisioned.
* Make one attempt at provisioning on-line, and if it fails
* thrown an exception.
*/
void requiresProvision();

/**
* This operation requires that the device is already provisioned.
* If it isn't then fail immediately without attempting any network
* communications.
*/
void requiresAlreadyProvisioned();

data::InstallationResult PackageInstall(const Uptane::Target &target);
std::pair<bool, Uptane::Target> downloadImage(const Uptane::Target &target,
const api::FlowControlToken *token = nullptr);
Expand Down
1 change: 1 addition & 0 deletions src/libaktualizr/primary/uptane_key_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class UptaneKey_Check_Test {
public:
static void checkKeyTests(std::shared_ptr<INvStorage>& storage, SotaUptaneClient& sota_client) {
EXPECT_NO_THROW(sota_client.initialize());
EXPECT_TRUE(sota_client.attemptProvision());
// Verify that TLS credentials are valid.
std::string ca;
std::string cert;
Expand Down
4 changes: 2 additions & 2 deletions tests/run_vector_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ while ! curl -I -s -f "http://localhost:$PORT"; do
done

if [[ -n $VALGRIND ]]; then
"$VALGRIND" "$UPTANE_VECTOR_TEST" "$PORT" "$@"
"$VALGRIND" "$UPTANE_VECTOR_TEST" "$PORT" "$TESTS_SRC_DIR" "$@"
else
"$UPTANE_VECTOR_TEST" "$PORT" "$@"
"$UPTANE_VECTOR_TEST" "$PORT" "$TESTS_SRC_DIR" "$@"
fi

RES=$?
Expand Down
53 changes: 41 additions & 12 deletions tests/uptane_vector_tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
#include "storage/invstorage.h"
#include "utilities/utils.h"

std::string address;
using std::string;

string address; // NOLINT
string tests_path; // NOLINT

class VectorWrapper {
public:
VectorWrapper(Json::Value vector) : vector_(std::move(vector)) {}
explicit VectorWrapper(Json::Value vector) : vector_(std::move(vector)) {}

bool matchError(const Uptane::Exception& e) {
auto me = [this, &e](const std::string r) {
auto me = [this, &e](const string r) {
if (vector_[r]["update"]["err_msg"].asString() == e.what()) {
return true;
}
Expand Down Expand Up @@ -81,7 +84,26 @@ class VectorWrapper {
Json::Value vector_;
};

class UptaneVector : public ::testing::TestWithParam<std::string> {};
class UptaneVector : public ::testing::TestWithParam<string> {};

class HttpWrapper : public HttpClient {
public:
HttpResponse post(const string& url, const string& content_type, const string& data) override {
if (url.find("/devices") != string::npos || url.find("/director/ecus") != string::npos) {
LOG_TRACE << " HttpWrapper intercepting device registration";
return {Utils::readFile(tests_path + "/test_data/cred.p12"), 200, CURLE_OK, ""};
}

if (url.find("/director/ecus") != string::npos) {
LOG_TRACE << " HttpWrapper intercepting Uptane ECU registration";
return {"", 200, CURLE_OK, ""};
}

LOG_TRACE << "HttpWrapper letting " << url << " pass";
return HttpClient::post(url, content_type, data);
}
HttpResponse post(const string& url, const Json::Value& data) override { return HttpClient::post(url, data); }
};

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

TemporaryDirectory temp_dir;
Config config;
config.provision.primary_ecu_serial = "test_primary_ecu_serial";
config.provision.primary_ecu_hardware_id = "test_primary_hardware_id";
config.provision.provision_path = tests_path + "/test_data/cred.zip";
config.provision.mode = ProvisionMode::kSharedCredReuse;
config.uptane.director_server = address + test_name + "/director";
config.uptane.repo_server = address + test_name + "/image_repo";
config.storage.path = temp_dir.Path();
config.storage.uptane_metadata_path = utils::BasedPath(temp_dir.Path() / "metadata");
config.pacman.images_path = temp_dir.Path() / "images";
config.pacman.type = PACKAGE_MANAGER_NONE;
config.postUpdateValues();
logger_set_threshold(boost::log::trivial::trace);

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

HttpClient http_client;
uptane_client->initialize();
ASSERT_TRUE(uptane_client->attemptProvision()) << "Provisioning Failed. Can't continue test";
while (true) {
HttpResponse response = http_client.post(address + test_name + "/step", Json::Value());
HttpResponse response = http_client->post(address + test_name + "/step", Json::Value());
if (response.http_status_code == 204) {
return;
}
Expand Down Expand Up @@ -172,10 +199,10 @@ TEST_P(UptaneVector, Test) {
FAIL() << "Step sequence unexpectedly aborted.";
}

std::vector<std::string> GetVectors() {
std::vector<string> GetVectors() {
HttpClient http_client;
const Json::Value json_vectors = http_client.get(address, HttpInterface::kNoLimit).getJson();
std::vector<std::string> vectors;
std::vector<string> vectors;
for (Json::ValueConstIterator it = json_vectors.begin(); it != json_vectors.end(); it++) {
vectors.emplace_back((*it).asString());
}
Expand All @@ -188,17 +215,19 @@ int main(int argc, char* argv[]) {
logger_init();
logger_set_threshold(boost::log::trivial::trace);

if (argc < 2) {
if (argc < 3) {
std::cerr << "This program is intended to be run from run_vector_tests.sh!\n";
return 1;
}

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

tests_path = argv[2];

::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

0 comments on commit 67c8555

Please sign in to comment.