Skip to content

Commit

Permalink
Merge pull request #332 from foundriesio/detsch-move-main-daemon
Browse files Browse the repository at this point in the history
Move aklite daemon function to a new file and add unit test for it
  • Loading branch information
detsch committed Jun 26, 2024
2 parents e153a59 + f5a9043 commit 43079a1
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 55 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ if(BUILD_AKLITE)
add_dependencies(aklite aktualizr-lite)

add_custom_target(aklite-tests)
add_dependencies(aklite-tests aklite t_lite-helpers uptane-generator t_compose-apps t_ostree t_liteclient t_yaml2json t_composeappengine t_restorableappengine t_aklite t_aklite_rollback t_aklite_rollback_ext t_apiclient t_exec t_docker t_aklite_offline t_boot_flag_mgmt t_cli t_nospace)
add_dependencies(aklite-tests aklite t_lite-helpers uptane-generator t_compose-apps t_ostree t_liteclient t_yaml2json t_composeappengine t_restorableappengine t_aklite t_aklite_rollback t_aklite_rollback_ext t_apiclient t_exec t_docker t_aklite_offline t_boot_flag_mgmt t_cli t_nospace t_daemon)

set(CMAKE_MODULE_PATH "${AKTUALIZR_DIR}/cmake-modules;${CMAKE_MODULE_PATH}")

Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(SRC helpers.cc
tuf/akhttpsreposource.cc
tuf/localreposource.cc
tuf/akrepo.cc
daemon.cc
aklitereportqueue.cc)

set(HEADERS helpers.h
Expand Down Expand Up @@ -51,6 +52,7 @@ set(HEADERS helpers.h
../include/aktualizr-lite/api.h
../include/aktualizr-lite/aklite_client_ext.h
../include/aktualizr-lite/tuf/tuf.h
daemon.h
aklitereportqueue.h)

if(USE_COMPOSEAPP_ENGINE)
Expand Down
63 changes: 63 additions & 0 deletions src/daemon.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <sys/file.h>
#include <memory>
#include <thread>

#include "aktualizr-lite/aklite_client_ext.h"
#include "aktualizr-lite/api.h"
#include "daemon.h"
#include "libaktualizr/config.h"
#include "liteclient.h"
#include "logging/logging.h"

int run_daemon(LiteClient& client, uint64_t interval, bool return_on_sleep, bool acquire_lock) {
if (client.config.uptane.repo_server.empty()) {
LOG_ERROR << "[uptane]/repo_server is not configured";
return EXIT_FAILURE;
}
if (access(client.config.bootloader.reboot_command.c_str(), X_OK) != 0) {
LOG_ERROR << "reboot command: " << client.config.bootloader.reboot_command << " is not executable";
return EXIT_FAILURE;
}

std::shared_ptr<LiteClient> client_ptr{&client, [](LiteClient* /*unused*/) {}};
AkliteClientExt akclient{client_ptr, false, acquire_lock};
if (akclient.IsInstallationInProgress()) {
auto finalize_result = akclient.CompleteInstallation();
if (finalize_result.status == InstallResult::Status::NeedsCompletion) {
LOG_ERROR << "A system reboot is required to finalize the pending installation.";
return EXIT_FAILURE;
}
}

Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id);

while (true) {
auto current = akclient.GetCurrent();
LOG_INFO << "Active Target: " << current.Name() << ", sha256: " << current.Sha256Hash();
LOG_INFO << "Checking for a new Target...";
auto cir = akclient.GetTargetToInstall();
if (!cir.selected_target.IsUnknown()) {
LOG_INFO << "Going to install " << cir.selected_target.Name() << ". Reason: " << cir.reason;
// A target is supposed to be installed
auto install_result = akclient.PullAndInstall(cir.selected_target, cir.reason);
if (akclient.RebootIfRequired()) {
// no point to continue running TUF cycle (check for update, download, install)
// since reboot is required to apply/finalize the currently installed update (aka pending update)
// If a reboot command is set in configuration, and is executed successfully, we will not get to this point
break;
}
if (install_result) {
// After a successful install, do not sleep, go directly to the next iteration
continue;
}
}

if (return_on_sleep) {
break;
}

std::this_thread::sleep_for(std::chrono::seconds(interval));
} // while true

return EXIT_SUCCESS;
}
10 changes: 10 additions & 0 deletions src/daemon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef AKTUALIZR_LITE_DAEMON_H_
#define AKTUALIZR_LITE_DAEMON_H_

#include <cstdint>

#include "aktualizr-lite/api.h"

int run_daemon(LiteClient& client, uint64_t interval, bool return_on_sleep, bool acquire_lock);

#endif // AKTUALIZR_LITE_DAEMON_H_
56 changes: 2 additions & 54 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "aktualizr-lite/api.h"
#include "aktualizr-lite/cli/cli.h"
#include "crypto/keymanager.h"
#include "daemon.h"
#include "helpers.h"
#include "http/httpclient.h"
#include "libaktualizr/config.h"
Expand Down Expand Up @@ -105,59 +106,6 @@ static int list_main(LiteClient& client, const bpo::variables_map& unused) {
return 0;
}

int daemon_main_(LiteClient& client, uint64_t interval, bool return_on_sleep) {
if (client.config.uptane.repo_server.empty()) {
LOG_ERROR << "[uptane]/repo_server is not configured";
return EXIT_FAILURE;
}
if (access(client.config.bootloader.reboot_command.c_str(), X_OK) != 0) {
LOG_ERROR << "reboot command: " << client.config.bootloader.reboot_command << " is not executable";
return EXIT_FAILURE;
}

std::shared_ptr<LiteClient> client_ptr{&client, [](LiteClient* /*unused*/) {}};
AkliteClientExt akclient{client_ptr, false, true};
if (akclient.IsInstallationInProgress()) {
auto finalize_result = akclient.CompleteInstallation();
if (finalize_result.status == InstallResult::Status::NeedsCompletion) {
LOG_ERROR << "A system reboot is required to finalize the pending installation.";
return EXIT_FAILURE;
}
}

Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id);

while (true) {
auto current = akclient.GetCurrent();
LOG_INFO << "Active Target: " << current.Name() << ", sha256: " << current.Sha256Hash();
LOG_INFO << "Checking for a new Target...";
auto cir = akclient.GetTargetToInstall();
if (!cir.selected_target.IsUnknown()) {
LOG_INFO << "Going to install " << cir.selected_target.Name() << ". Reason: " << cir.reason;
// A target is supposed to be installed
auto install_result = akclient.PullAndInstall(cir.selected_target, cir.reason);
if (akclient.RebootIfRequired()) {
// no point to continue running TUF cycle (check for update, download, install)
// since reboot is required to apply/finalize the currently installed update (aka pending update)
// If a reboot command is set in configuration, and is executed successfully, we will not get to this point
break;
}
if (install_result) {
// After a successful install, do not sleep, go directly to the next iteration
continue;
}
}

if (return_on_sleep) {
break;
}

std::this_thread::sleep_for(std::chrono::seconds(interval));
} // while true

return EXIT_SUCCESS;
}

static int daemon_main(LiteClient& client, const bpo::variables_map& variables_map) {
uint64_t interval = client.config.uptane.polling_sec;
if (variables_map.count("interval") > 0) {
Expand All @@ -166,7 +114,7 @@ static int daemon_main(LiteClient& client, const bpo::variables_map& variables_m

bool return_on_sleep = std::getenv("AKLITE_TEST_RETURN_ON_SLEEP") != nullptr;

return daemon_main_(client, interval, return_on_sleep);
return run_daemon(client, interval, return_on_sleep, true);
}

static int cli_install(LiteClient& client, const bpo::variables_map& params) {
Expand Down
14 changes: 14 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,17 @@ set_tests_properties(test_nospace PROPERTIES LABELS "aklite:nospace")
aktualizr_source_file_checks(nospace_test.cc)

add_library(fstatvfs-mock SHARED fixtures/fstatvfs-mock.cc)

add_aktualizr_test(NAME daemon
SOURCES daemon_test.cc
PROJECT_WORKING_DIRECTORY
ARGS
${PROJECT_SOURCE_DIR}/tests/device-gateway_fake.py
${PROJECT_SOURCE_DIR}/tests/make_sys_rootfs.sh
)
aktualizr_source_file_checks(daemon_test.cc)
target_compile_definitions(t_daemon PRIVATE ${TEST_DEFS})
target_include_directories(t_daemon PRIVATE ${TEST_INCS} ${AKLITE_DIR}/include ${AKTUALIZR_DIR}/tests/ ${AKTUALIZR_DIR}/src/)
target_link_libraries(t_daemon ${MAIN_TARGET_LIB} ${TEST_LIBS} uptane_generator_lib testutilities)
add_dependencies(t_daemon make_ostree_sysroot)
set_tests_properties(test_daemon PROPERTIES LABELS "aklite:daemon")
113 changes: 113 additions & 0 deletions tests/daemon_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <boost/filesystem.hpp>
#include <boost/process.hpp>
#include <boost/process/env.hpp>

#include "libaktualizr/types.h"
#include "logging/logging.h"
#include "test_utils.h"
#include "uptane_generator/image_repo.h"
#include "utilities/utils.h"

#include "aktualizr-lite/api.h"
#include "composeappmanager.h"
#include "daemon.h"
#include "liteclient.h"

#include "fixtures/liteclienttest.cc"

using ::testing::NiceMock;
using ::testing::Return;

/**
* Class MockAppEngine
*
*/
class MockAppEngine : public AppEngine {
public:
MockAppEngine(bool default_behaviour = true) {
if (!default_behaviour) return;

ON_CALL(*this, fetch).WillByDefault(Return(true));
ON_CALL(*this, verify).WillByDefault(Return(true));
ON_CALL(*this, install).WillByDefault(Return(true));
ON_CALL(*this, run).WillByDefault(Return(true));
ON_CALL(*this, isFetched).WillByDefault(Return(true));
ON_CALL(*this, isRunning).WillByDefault(Return(true));
ON_CALL(*this, getRunningAppsInfo)
.WillByDefault(
Return(Utils::parseJSON("{\"app-07\": {\"services\": {\"nginx-07\": {\"hash\": "
"\"16e36b4ab48cb19c7100a22686f85ffcbdce5694c936bda03cb12a2cce88efcf\"}}}}")));
}

public:
MOCK_METHOD(AppEngine::Result, fetch, (const App& app), (override));
MOCK_METHOD(AppEngine::Result, verify, (const App& app), (override));
MOCK_METHOD(AppEngine::Result, install, (const App& app), (override));
MOCK_METHOD(AppEngine::Result, run, (const App& app), (override));
MOCK_METHOD(void, stop, (const App& app), (override));
MOCK_METHOD(void, remove, (const App& app), (override));
MOCK_METHOD(bool, isFetched, (const App& app), (const, override));
MOCK_METHOD(bool, isRunning, (const App& app), (const, override));
MOCK_METHOD(AppEngine::Apps, getInstalledApps, (), (const, override));
MOCK_METHOD(Json::Value, getRunningAppsInfo, (), (const, override));
MOCK_METHOD(void, prune, (const Apps& app), (override));
};

class DaemonTest : public fixtures::ClientTest {
protected:
std::shared_ptr<LiteClient> createLiteClient(InitialVersion initial_version = InitialVersion::kOn,
boost::optional<std::vector<std::string>> apps = boost::none,
bool finalize = true) override {
app_engine_mock_ = std::make_shared<NiceMock<MockAppEngine>>();
lite_client_ = ClientTest::createLiteClient(app_engine_mock_, initial_version, apps);
return lite_client_;
}

private:
std::shared_ptr<NiceMock<MockAppEngine>> app_engine_mock_;
std::shared_ptr<LiteClient> lite_client_;
std::string pacman_type_;
};

TEST_F(DaemonTest, MainDaemonOstreeInstall) {
auto liteclient = createLiteClient();
ASSERT_TRUE(targetsMatch(liteclient->getCurrent(), getInitialTarget()));

// Create a new Target: update rootfs and commit it into Treehub's repo
auto new_target = createTarget();

// Run one iteration of daemon code: install should be successful, requiring a reboot
auto daemon_ret = run_daemon(*liteclient, 100, true, false);
ASSERT_EQ(daemon_ret, EXIT_SUCCESS);

// Trying to run daemon again before rebooting leads to an error, and the original target still running
daemon_ret = run_daemon(*liteclient, 100, true, false);
ASSERT_EQ(daemon_ret, EXIT_FAILURE);
ASSERT_TRUE(targetsMatch(liteclient->getCurrent(), getInitialTarget()));

reboot(liteclient);

// After reboot, running the daemon again finishes the installation, the function returns successfully,
// and the new target becomes the current one
daemon_ret = run_daemon(*liteclient, 100, true, false);
ASSERT_EQ(daemon_ret, EXIT_SUCCESS);
ASSERT_TRUE(targetsMatch(liteclient->getCurrent(), new_target));
}

int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << argv[0] << " invalid arguments\n";
return EXIT_FAILURE;
}

::testing::InitGoogleTest(&argc, argv);
logger_init();

// options passed as args in CMakeLists.txt
fixtures::DeviceGatewayMock::RunCmd = argv[1];
fixtures::SysRootFS::CreateCmd = argv[2];
return RUN_ALL_TESTS();
}

0 comments on commit 43079a1

Please sign in to comment.