diff --git a/include/libaktualizr/aktualizr.h b/include/libaktualizr/aktualizr.h index b676f10e5..5b268212f 100644 --- a/include/libaktualizr/aktualizr.h +++ b/include/libaktualizr/aktualizr.h @@ -41,8 +41,12 @@ class Aktualizr { /** * Initialize aktualizr. Any Secondaries should be added before making this - * call. This will provision with the server if required. This must be called - * before using any other aktualizr functions except AddSecondary. + * call. This must be called before using any other aktualizr functions + * except AddSecondary. + * + * Provisioning will be attempted once if it hasn't already been completed. + * If that fails (for example because there is no network), then provisioning + * will be automatically re-attempted ahead of any operation that requires it. * * @throw Initializer::Error and subclasses * @throw SQLException @@ -86,6 +90,7 @@ class Aktualizr { * * @throw std::bad_alloc (memory allocation failure) * @throw std::runtime_error (curl failure) + * @throw SotaUptaneClient::ProvisioningFailed (on-line provisioning failed) */ std::future CampaignCheck(); @@ -114,6 +119,7 @@ class Aktualizr { * @throw std::bad_alloc (memory allocation failure) * @throw std::runtime_error (curl and filesystem failures) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::ProvisioningFailed (on-line provisioning failed) */ std::future SendDeviceData(); @@ -130,6 +136,7 @@ class Aktualizr { * @throw std::runtime_error (curl and filesystem failures; database * inconsistency with pending updates) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::ProvisioningFailed (on-line provisioning failed) */ std::future CheckUpdates(); @@ -141,6 +148,7 @@ class Aktualizr { * @throw SQLException * @throw std::bad_alloc (memory allocation failure) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::NotProvisionedYet (called before provisioning complete) */ std::future Download(const std::vector& updates); @@ -207,6 +215,7 @@ class Aktualizr { * @throw std::bad_alloc (memory allocation failure) * @throw std::runtime_error (error getting metadata from database or filesystem) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::NotProvisionedYet (called before provisioning complete) */ std::future Install(const std::vector& updates); @@ -238,6 +247,7 @@ class Aktualizr { * @throw std::bad_alloc (memory allocation failure) * @throw std::runtime_error (curl failure; database inconsistency with pending updates) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::ProvisioningFailed (on-line provisioning failed) */ std::future SendManifest(const Json::Value& custom = Json::nullValue); @@ -288,6 +298,7 @@ class Aktualizr { * inconsistency with pending updates; error * getting metadata from database or filesystem) * @throw std::system_error (failure to lock a mutex) + * @throw SotaUptaneClient::ProvisioningFailed (on-line provisioning failed) */ bool UptaneCycle(); diff --git a/src/libaktualizr/primary/aktualizr.cc b/src/libaktualizr/primary/aktualizr.cc index f74b5c9d6..f89b7f609 100644 --- a/src/libaktualizr/primary/aktualizr.cc +++ b/src/libaktualizr/primary/aktualizr.cc @@ -73,12 +73,21 @@ bool Aktualizr::UptaneCycle() { std::future Aktualizr::RunForever() { std::future future = std::async(std::launch::async, [this]() { - SendDeviceData().get(); - std::unique_lock l(exit_cond_.m); + bool have_sent_device_data = false; while (true) { - if (!UptaneCycle()) { - break; + try { + if (!have_sent_device_data) { + // Can throw SotaUptaneClient::ProvisioningFailed + SendDeviceData().get(); + have_sent_device_data = true; + } + + if (!UptaneCycle()) { + break; + } + } catch (SotaUptaneClient::ProvisioningFailed &e) { + LOG_DEBUG << "Not provisioned yet:" << e.what(); } if (exit_cond_.cv.wait_for(l, std::chrono::seconds(config_.uptane.polling_sec), diff --git a/src/libaktualizr/primary/sotauptaneclient.cc b/src/libaktualizr/primary/sotauptaneclient.cc index 542b4610b..c7053570d 100644 --- a/src/libaktualizr/primary/sotauptaneclient.cc +++ b/src/libaktualizr/primary/sotauptaneclient.cc @@ -398,26 +398,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(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 ProvisioningFailed(); + } +} + +void SotaUptaneClient::requiresAlreadyProvisioned() { + if (provisioner_.CurrentState() != Provisioner::State::kOk) { + throw NotProvisionedYet(); } } void SotaUptaneClient::updateDirectorMeta() { + requiresProvision(); try { director_repo.updateMeta(*storage, *uptane_fetcher); } catch (const std::exception &e) { @@ -427,6 +430,7 @@ void SotaUptaneClient::updateDirectorMeta() { } void SotaUptaneClient::updateImageMeta() { + requiresProvision(); try { image_repo.updateMeta(*storage, *uptane_fetcher); } catch (const std::exception &e) { @@ -436,6 +440,7 @@ void SotaUptaneClient::updateImageMeta() { } void SotaUptaneClient::checkDirectorMetaOffline() { + requiresAlreadyProvisioned(); try { director_repo.checkMetaOffline(*storage); } catch (const std::exception &e) { @@ -445,6 +450,7 @@ void SotaUptaneClient::checkDirectorMetaOffline() { } void SotaUptaneClient::checkImageMetaOffline() { + requiresAlreadyProvisioned(); try { image_repo.checkMetaOffline(*storage); } catch (const std::exception &e) { @@ -661,6 +667,7 @@ std::unique_ptr SotaUptaneClient::findTargetInDelegationTree(con result::Download SotaUptaneClient::downloadImages(const std::vector &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 guard(download_mutex); @@ -838,6 +845,8 @@ void SotaUptaneClient::uptaneOfflineIteration(std::vector *targe } void SotaUptaneClient::sendDeviceData() { + requiresProvision(); + reportHwInfo(); reportInstalledPackages(); reportNetworkInfo(); @@ -846,6 +855,8 @@ void SotaUptaneClient::sendDeviceData() { } result::UpdateCheck SotaUptaneClient::fetchMeta() { + requiresProvision(); + result::UpdateCheck result; reportNetworkInfo(); @@ -987,6 +998,7 @@ result::UpdateStatus SotaUptaneClient::checkUpdatesOffline(const std::vector &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 @@ -1103,6 +1115,8 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vector(); report_queue->enqueue(std_::make_unique(campaign_id)); } void SotaUptaneClient::campaignDecline(const std::string &campaign_id) { + requiresAlreadyProvisioned(); + sendEvent(); report_queue->enqueue(std_::make_unique(campaign_id)); } void SotaUptaneClient::campaignPostpone(const std::string &campaign_id) { + requiresAlreadyProvisioned(); + sendEvent(); report_queue->enqueue(std_::make_unique(campaign_id)); } @@ -1182,6 +1202,8 @@ bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) { } bool SotaUptaneClient::putManifest(const Json::Value &custom) { + requiresProvision(); + bool success = putManifestSimple(custom); sendEvent(success); return success; diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h index 70f1a1b40..cc3a95b9f 100644 --- a/src/libaktualizr/primary/sotauptaneclient.h +++ b/src/libaktualizr/primary/sotauptaneclient.h @@ -35,6 +35,24 @@ class SotaUptaneClient { public: + /** + * Provisioning was needed, attempted and failed. + * Thrown by requiresProvision(). + */ + class ProvisioningFailed : public std::runtime_error { + public: + explicit ProvisioningFailed() : std::runtime_error("Device was not able provision on-line") {} + }; + + /** + * Device must be provisioned before calling this operation. + * Thrown by requiresAlreadyProvisioned(). + */ + class NotProvisionedYet : public std::runtime_error { + public: + explicit NotProvisionedYet() : std::runtime_error("Device is not provisioned on-line yet") {} + }; + SotaUptaneClient(Config &config_in, std::shared_ptr storage_in, std::shared_ptr http_in, std::shared_ptr events_channel_in); @@ -109,6 +127,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 throw a + * ProvisioningFailed exception. + */ + void requiresProvision(); + + /** + * This operation requires that the device is already provisioned. + * If it isn't then immediately throw a NotProvisionedYet exception without + * attempting any network communications. + */ + void requiresAlreadyProvisioned(); + data::InstallationResult PackageInstall(const Uptane::Target &target); std::pair downloadImage(const Uptane::Target &target, const api::FlowControlToken *token = nullptr); diff --git a/src/libaktualizr/primary/uptane_key_test.cc b/src/libaktualizr/primary/uptane_key_test.cc index 3a5a356a4..6c88355bb 100644 --- a/src/libaktualizr/primary/uptane_key_test.cc +++ b/src/libaktualizr/primary/uptane_key_test.cc @@ -55,6 +55,7 @@ class UptaneKey_Check_Test { public: static void checkKeyTests(std::shared_ptr& 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; diff --git a/tests/run_vector_tests.sh b/tests/run_vector_tests.sh index afa54e593..af8a20e71 100755 --- a/tests/run_vector_tests.sh +++ b/tests/run_vector_tests.sh @@ -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=$? diff --git a/tests/uptane_vector_tests.cc b/tests/uptane_vector_tests.cc index e97966969..81e1f1348 100644 --- a/tests/uptane_vector_tests.cc +++ b/tests/uptane_vector_tests.cc @@ -1,8 +1,5 @@ #include -#include -#include -#include #include #include #include @@ -15,14 +12,17 @@ #include "storage/invstorage.h" #include "utilities/utils.h" -std::string address; +using std::string; + +string address; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +string tests_path; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 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; } @@ -81,7 +81,26 @@ class VectorWrapper { Json::Value vector_; }; -class UptaneVector : public ::testing::TestWithParam {}; +class UptaneVector : public ::testing::TestWithParam {}; + +class HttpWrapper : public HttpClient { + public: + HttpResponse post(const string& url, const string& content_type, const string& data) override { + if (url.find("/devices") != 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. @@ -90,23 +109,27 @@ class UptaneVector : public ::testing::TestWithParam {}; * 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(config, storage); + auto http_client = std::make_shared(); + auto uptane_client = std_::make_unique(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); @@ -115,9 +138,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; } @@ -172,10 +196,10 @@ TEST_P(UptaneVector, Test) { FAIL() << "Step sequence unexpectedly aborted."; } -std::vector GetVectors() { +std::vector GetVectors() { HttpClient http_client; const Json::Value json_vectors = http_client.get(address, HttpInterface::kNoLimit).getJson(); - std::vector vectors; + std::vector vectors; for (Json::ValueConstIterator it = json_vectors.begin(); it != json_vectors.end(); it++) { vectors.emplace_back((*it).asString()); } @@ -188,7 +212,7 @@ 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; } @@ -196,9 +220,11 @@ int main(int argc, char* argv[]) { /* 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(); }