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 30, 2022
1 parent a28317a commit 994f256
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 32 deletions.
15 changes: 13 additions & 2 deletions include/libaktualizr/aktualizr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<result::CampaignCheck> CampaignCheck();

Expand Down Expand Up @@ -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<void> SendDeviceData();

Expand All @@ -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<result::UpdateCheck> CheckUpdates();

Expand All @@ -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<result::Download> Download(const std::vector<Uptane::Target>& updates);

Expand Down Expand Up @@ -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<result::Install> Install(const std::vector<Uptane::Target>& updates);

Expand Down Expand Up @@ -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<bool> SendManifest(const Json::Value& custom = Json::nullValue);

Expand Down Expand Up @@ -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();

Expand Down
17 changes: 13 additions & 4 deletions src/libaktualizr/primary/aktualizr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,21 @@ bool Aktualizr::UptaneCycle() {

std::future<void> Aktualizr::RunForever() {
std::future<void> future = std::async(std::launch::async, [this]() {
SendDeviceData().get();

std::unique_lock<std::mutex> 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),
Expand Down
40 changes: 31 additions & 9 deletions src/libaktualizr/primary/sotauptaneclient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 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) {
Expand All @@ -427,6 +430,7 @@ void SotaUptaneClient::updateDirectorMeta() {
}

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

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

void SotaUptaneClient::checkImageMetaOffline() {
requiresAlreadyProvisioned();
try {
image_repo.checkMetaOffline(*storage);
} catch (const std::exception &e) {
Expand Down Expand Up @@ -661,6 +667,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 @@ -838,6 +845,8 @@ void SotaUptaneClient::uptaneOfflineIteration(std::vector<Uptane::Target> *targe
}

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

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

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

result::UpdateCheck result;

reportNetworkInfo();
Expand Down Expand Up @@ -987,6 +998,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 @@ -1103,6 +1115,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 @@ -1117,16 +1131,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 @@ -1182,6 +1202,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
32 changes: 32 additions & 0 deletions src/libaktualizr/primary/sotauptaneclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<INvStorage> storage_in, std::shared_ptr<HttpInterface> http_in,
std::shared_ptr<event::Channel> events_channel_in);

Expand Down Expand Up @@ -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<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
Loading

0 comments on commit 994f256

Please sign in to comment.