From b853cbb690763cb738ac4d6a5b88e247c1f09126 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Tue, 23 Apr 2019 12:29:52 +0200 Subject: [PATCH 1/9] Put fake sentinel dir in common in tests Signed-off-by: Laurent Bonnans --- src/libaktualizr/primary/aktualizr_test.cc | 5 ----- src/libaktualizr/primary/empty_targets_test.cc | 1 - tests/uptane_test_common.h | 1 + 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libaktualizr/primary/aktualizr_test.cc b/src/libaktualizr/primary/aktualizr_test.cc index 5360496d0c..5e40380bce 100644 --- a/src/libaktualizr/primary/aktualizr_test.cc +++ b/src/libaktualizr/primary/aktualizr_test.cc @@ -409,7 +409,6 @@ TEST(Aktualizr, FullWithUpdatesNeedReboot) { auto http = std::make_shared(temp_dir.Path()); Config conf = UptaneTestCommon::makeTestConfig(temp_dir, http->tls_server); conf.pacman.fake_need_reboot = true; - conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); { // first run: do the install @@ -602,7 +601,6 @@ TEST(Aktualizr, FinalizationFailure) { conf.pacman.fake_need_reboot = true; conf.uptane.force_install_completion = true; conf.uptane.polling_sec = 0; - conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); auto storage = INvStorage::newStorage(conf.storage); std::vector expected_event_order = { @@ -781,7 +779,6 @@ TEST(Aktualizr, InstallationFailure) { TemporaryDirectory temp_dir; auto http_server_mock = std::make_shared(temp_dir.Path()); Config conf = UptaneTestCommon::makeTestConfig(temp_dir, http_server_mock->tls_server); - conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); auto storage = INvStorage::newStorage(conf.storage); fiu_init(0); @@ -841,7 +838,6 @@ TEST(Aktualizr, InstallationFailure) { TemporaryDirectory temp_dir; auto http_server_mock = std::make_shared(temp_dir.Path()); Config conf = UptaneTestCommon::makeTestConfig(temp_dir, http_server_mock->tls_server); - conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); auto storage = INvStorage::newStorage(conf.storage); const std::string sec_fault_name = std::string("secondary_install_") + secondary_ecu_id; @@ -927,7 +923,6 @@ TEST(Aktualizr, AutoRebootAfterUpdate) { conf.pacman.fake_need_reboot = true; conf.uptane.force_install_completion = true; conf.uptane.polling_sec = 0; - conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); { // first run: do the install, exit UptaneCycle and emulate reboot since force_install_completion is set diff --git a/src/libaktualizr/primary/empty_targets_test.cc b/src/libaktualizr/primary/empty_targets_test.cc index a37b6fd0e6..a0e39c53fa 100644 --- a/src/libaktualizr/primary/empty_targets_test.cc +++ b/src/libaktualizr/primary/empty_targets_test.cc @@ -41,7 +41,6 @@ TEST(Aktualizr, EmptyTargets) { auto http = std::make_shared(temp_dir.Path(), meta_dir.Path() / "repo"); Config conf = UptaneTestCommon::makeTestConfig(temp_dir, http->tls_server); conf.pacman.fake_need_reboot = true; - conf.bootloader.reboot_sentinel_dir = temp_dir / "aktualizr-session"; logger_set_threshold(boost::log::trivial::trace); Process akt_repo(aktualizr_repo_path.string()); diff --git a/tests/uptane_test_common.h b/tests/uptane_test_common.h index 50132b4477..440b9cff6e 100644 --- a/tests/uptane_test_common.h +++ b/tests/uptane_test_common.h @@ -38,6 +38,7 @@ struct UptaneTestCommon { conf.provision.primary_ecu_hardware_id = "primary_hw"; conf.storage.path = temp_dir.Path(); conf.tls.server = url; + conf.bootloader.reboot_sentinel_dir = temp_dir.Path(); UptaneTestCommon::addDefaultSecondary(conf, temp_dir, "secondary_ecu_serial", "secondary_hw"); return conf; } From 9acff26e30a48d8dd5e11d2548109ba940bae5b2 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Tue, 23 Apr 2019 17:37:23 +0200 Subject: [PATCH 2/9] Set image format in aktualizr-repo Also, rename addImage to addBinaryImage Signed-off-by: Laurent Bonnans --- src/aktualizr_repo/image_repo.cc | 55 ++++++++++++++++--------------- src/aktualizr_repo/image_repo.h | 2 +- src/aktualizr_repo/uptane_repo.cc | 2 +- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/aktualizr_repo/image_repo.cc b/src/aktualizr_repo/image_repo.cc index 15b13cb257..466b79f943 100644 --- a/src/aktualizr_repo/image_repo.cc +++ b/src/aktualizr_repo/image_repo.cc @@ -1,6 +1,21 @@ #include "image_repo.h" -void ImageRepo::addImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, +void ImageRepo::addImage(const std::string &name, const Json::Value &target, const Delegation &delegation) { + boost::filesystem::path repo_dir(path_ / "repo/image"); + + boost::filesystem::path targets_path = + delegation ? ((repo_dir / "delegations") / delegation.name).string() + ".json" : repo_dir / "targets.json"; + Json::Value targets = Utils::parseJSONFile(targets_path)["signed"]; + targets["targets"][name] = target; + targets["version"] = (targets["version"].asUInt()) + 1; + + auto role = delegation ? Uptane::Role(delegation.name, true) : Uptane::Role::Targets(); + std::string signed_targets = Utils::jsonToCanonicalStr(signTuf(role, targets)); + Utils::writeFile(targets_path, signed_targets); + updateRepo(); +} + +void ImageRepo::addBinaryImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, const Delegation &delegation) { boost::filesystem::path repo_dir(path_ / "repo/image"); @@ -18,22 +33,21 @@ void ImageRepo::addImage(const boost::filesystem::path &image_path, const boost: target["length"] = Json::UInt64(image.size()); target["hashes"]["sha256"] = boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(image))); target["hashes"]["sha512"] = boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(image))); + target["custom"]["targetFormat"] = "BINARY"; addImage(targetname.string(), target, delegation); } -void ImageRepo::addImage(const std::string &name, const Json::Value &target, const Delegation &delegation) { - boost::filesystem::path repo_dir(path_ / "repo/image"); - - boost::filesystem::path targets_path = - delegation ? ((repo_dir / "delegations") / delegation.name).string() + ".json" : repo_dir / "targets.json"; - Json::Value targets = Utils::parseJSONFile(targets_path)["signed"]; - targets["targets"][name] = target; - targets["version"] = (targets["version"].asUInt()) + 1; - - auto role = delegation ? Uptane::Role(delegation.name, true) : Uptane::Role::Targets(); - std::string signed_targets = Utils::jsonToCanonicalStr(signTuf(role, targets)); - Utils::writeFile(targets_path, signed_targets); - updateRepo(); +void ImageRepo::addCustomImage(const std::string &name, const Uptane::Hash &hash, const uint64_t length, + const Delegation &delegation, const Json::Value &custom) { + Json::Value target; + target["length"] = Json::UInt(length); + if (hash.type() == Uptane::Hash::Type::kSha256) { + target["hashes"]["sha256"] = hash.HashString(); + } else if (hash.type() == Uptane::Hash::Type::kSha512) { + target["hashes"]["sha512"] = hash.HashString(); + } + target["custom"] = custom; + addImage(name, target, delegation); } void ImageRepo::addDelegation(const Uptane::Role &name, const Uptane::Role &parent_role, const std::string &path, @@ -137,16 +151,3 @@ std::vector ImageRepo::getDelegationTargets(const Uptane::Role &nam } return result; } - -void ImageRepo::addCustomImage(const std::string &name, const Uptane::Hash &hash, const uint64_t length, - const Delegation &delegation, const Json::Value &custom) { - Json::Value target; - target["length"] = Json::UInt(length); - if (hash.type() == Uptane::Hash::Type::kSha256) { - target["hashes"]["sha256"] = hash.HashString(); - } else if (hash.type() == Uptane::Hash::Type::kSha512) { - target["hashes"]["sha512"] = hash.HashString(); - } - target["custom"] = custom; - addImage(name, target, delegation); -} diff --git a/src/aktualizr_repo/image_repo.h b/src/aktualizr_repo/image_repo.h index 30f920ed46..0b6b6050fe 100644 --- a/src/aktualizr_repo/image_repo.h +++ b/src/aktualizr_repo/image_repo.h @@ -7,7 +7,7 @@ class ImageRepo : public Repo { public: ImageRepo(boost::filesystem::path path, const std::string &expires, std::string correlation_id) : Repo(Uptane::RepositoryType::Image(), std::move(path), expires, std::move(correlation_id)) {} - void addImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, + void addBinaryImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, const Delegation &delegation = {}); void addCustomImage(const std::string &name, const Uptane::Hash &hash, uint64_t length, const Delegation &delegation, const Json::Value &custom = {}); diff --git a/src/aktualizr_repo/uptane_repo.cc b/src/aktualizr_repo/uptane_repo.cc index 1e3595f3f6..88b4b39b4f 100644 --- a/src/aktualizr_repo/uptane_repo.cc +++ b/src/aktualizr_repo/uptane_repo.cc @@ -30,7 +30,7 @@ void UptaneRepo::revokeDelegation(const Uptane::Role &name) { void UptaneRepo::addImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, const Delegation &delegation) { - image_repo_.addImage(image_path, targetname, delegation); + image_repo_.addBinaryImage(image_path, targetname, delegation); } void UptaneRepo::addCustomImage(const std::string &name, const Uptane::Hash &hash, uint64_t length, const Delegation &delegation, const Json::Value &custom) { From bb0ee63f5332515737c2d2ba75a804d94da6f918 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Thu, 25 Apr 2019 17:26:56 +0200 Subject: [PATCH 3/9] Add ability to specify image format in aktualizr-repo Signed-off-by: Laurent Bonnans --- src/aktualizr_repo/main.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aktualizr_repo/main.cc b/src/aktualizr_repo/main.cc index 200dc9cc60..1f834e547b 100644 --- a/src/aktualizr_repo/main.cc +++ b/src/aktualizr_repo/main.cc @@ -36,6 +36,7 @@ int main(int argc, char **argv) { ("path", po::value(), "path to the repository") ("filename", po::value(), "path to the image") ("hwid", po::value(), "target hardware identifier") + ("targetformat", po::value(), "format of target for 'image' command") ("targetcustom", po::value(), "path to custom JSON for 'image' command") ("serial", po::value(), "target ECU serial") ("expires", po::value(), "expiration time") @@ -117,9 +118,16 @@ int main(int argc, char **argv) { hash = std_::make_unique(Uptane::Hash::Type::kSha512, vm["targetsha512"].as()); } Json::Value custom; + if (vm.count("targetcustom") > 0 && vm.count("targetformat") > 0) { + std::cerr << "--targetcustom and --targetformat cannot be used together"; + exit(EXIT_FAILURE); + } if (vm.count("targetcustom") > 0) { std::ifstream custom_file(vm["targetcustom"].as().c_str()); custom_file >> custom; + } else if (vm.count("targetformat") > 0) { + custom = Json::Value(); + custom["targetFormat"] = vm["targetformat"].as(); } repo.addCustomImage(targetname.string(), *hash, vm["targetlength"].as(), delegation, custom); } From ebb30d58426a6d715142b69449616d9ec62c3558 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Fri, 26 Apr 2019 10:51:29 +0200 Subject: [PATCH 4/9] Declare stream << operator for Config publicly Signed-off-by: Laurent Bonnans --- src/libaktualizr/config/config.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libaktualizr/config/config.h b/src/libaktualizr/config/config.h index 96f4aa4d56..00d386ca62 100644 --- a/src/libaktualizr/config/config.h +++ b/src/libaktualizr/config/config.h @@ -108,4 +108,6 @@ class Config : public BaseConfig { bool loglevel_from_cmdline{false}; }; +std::ostream& operator<<(std::ostream& os, const Config& cfg); + #endif // CONFIG_H_ From ab8199790f4dcd0d5be918c39c7f359f20438354 Mon Sep 17 00:00:00 2001 From: Patrick Vacek Date: Thu, 18 Apr 2019 11:18:54 +0200 Subject: [PATCH 5/9] Add another HTTP server to make manual testing easier. I was using it for a unit test but decided it wasn't actually useful for that, but it still may be useful for other purposes. Signed-off-by: Patrick Vacek --- tests/fake_http_server/fake_test_server.py | 149 +++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100755 tests/fake_http_server/fake_test_server.py diff --git a/tests/fake_http_server/fake_test_server.py b/tests/fake_http_server/fake_test_server.py new file mode 100755 index 0000000000..795c455dc4 --- /dev/null +++ b/tests/fake_http_server/fake_test_server.py @@ -0,0 +1,149 @@ +#!/usr/bin/python3 + +import sys +import socket +from http.server import SimpleHTTPRequestHandler, HTTPServer +from time import sleep + +class Handler(SimpleHTTPRequestHandler): + def do_GET(self): + + if self.path.startswith("/director/") and self.path.endswith(".json"): + self.send_response(200) + self.end_headers() + role = self.path[len("/director/"):] + with open(meta_path + '/repo/director/' + role, 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + return + elif self.path.startswith("/repo/") and self.path.endswith(".json"): + self.send_response(200) + self.end_headers() + role = self.path[len("/repo/"):] + with open(meta_path + '/repo/image/' + role, 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + return + elif self.path.startswith("/repo/targets"): + self.send_response(200) + self.end_headers() + filename = self.path[len("/repo/targets"):] + with open(file_path + filename, 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + return + + if self.path == '/download': + self.send_response(301) + self.send_header('Location', '/download/file') + self.end_headers() + elif self.path == '/download/file': + self.send_response(200) + self.end_headers() + self.wfile.write(b'content') + elif self.path == '/auth_call': + self.send_response(200) + self.end_headers() + if 'Authorization' in self.headers: + auth_list = self.headers['Authorization'].split(' ') + if auth_list[0] == 'Bearer' and auth_list[1] == 'token': + self.wfile.write(b'{"status": "good"}') + self.wfile.write(b'{}') + elif self.path.endswith('/large_file'): + chunk_size = 1 << 20 + response_size = 100 * chunk_size + if "Range" in self.headers: + r = self.headers["Range"] + r_from = int(r.split("=")[1].split("-")[0]) + self.send_response(206) + self.send_header('Content-Range', 'bytes %d-%d/%d' %(r_from, response_size-1, response_size)) + response_size = response_size - r_from + else: + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Content-Length', response_size) + self.end_headers() + num_chunks, last_chunk = divmod(response_size, chunk_size) + b = b'@' * chunk_size + try: + while num_chunks > 0: + self.wfile.write(b) + num_chunks -= 1 + self.wfile.write(b'@' * last_chunk) + except ConnectionResetError: + return + elif self.path == '/slow_file': + self.send_response(200) + self.end_headers() + for i in range(5): + self.wfile.write(b'aa') + sleep(1) + else: + self.send_response(200) + self.end_headers() + self.wfile.write(b'{"path": "%b"}'%bytes(self.path, "utf8")) + + def do_POST(self): + if self.path == '/devices': + self.send_response(200) + self.end_headers() + with open('tests/test_data/cred.p12', 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + + elif self.path == "/director/ecus": + self.send_response(200) + self.end_headers() + self.wfile.write(b"{}") + return + + elif self.path == "/director/manifest": + self.send_response(200) + self.end_headers() + self.wfile.write(b"{}") + return + + elif self.path == '/token': + self.send_response(200) + self.end_headers() + if 'Authorization' in self.headers: + self.wfile.write(b"{\"access_token\": \"token\"}") + else: + self.wfile.write(b'') + + else: + self.send_response(404) + self.end_headers() + + def do_PUT(self): + self.do_POST() + + +class ReUseHTTPServer(HTTPServer): + def server_bind(self): + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + HTTPServer.server_bind(self) + + +server_address = ('', int(sys.argv[1])) +file_path = sys.argv[2] +meta_path = sys.argv[3] + +httpd = ReUseHTTPServer(server_address, Handler) +try: + httpd.serve_forever() +except KeyboardInterrupt: + httpd.server_close() + From a1d551e445724b6dec90ccf5f4417a7ede0c8993 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Fri, 26 Apr 2019 14:48:59 +0200 Subject: [PATCH 6/9] Add ostree mock dynamic library To be added to LD_PRELOAD for advanced mocking Signed-off-by: Laurent Bonnans --- tests/CMakeLists.txt | 3 +++ tests/ostree_mock.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/ostree_mock.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0aa404c9bb..f5cfcdd818 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,9 @@ include(CMakeParseArguments) add_library(testutilities test_utils.cc) +add_library(ostree_mock SHARED ostree_mock.c) +aktualizr_source_file_checks(ostree_mock.c) + # Setup coverage if(BUILD_WITH_CODE_COVERAGE) add_definitions(${COVERAGE_COMPILER_FLAGS}) diff --git a/tests/ostree_mock.c b/tests/ostree_mock.c new file mode 100644 index 0000000000..46b7f8e60c --- /dev/null +++ b/tests/ostree_mock.c @@ -0,0 +1,28 @@ +#define _GNU_SOURCE +#include +#include +#include + +OstreeDeployment *ostree_sysroot_get_booted_deployment (OstreeSysroot *self) { + OstreeDeployment* (*orig)(OstreeSysroot*) = dlsym(RTLD_NEXT, __func__); + char mocked_name[100]; + snprintf(mocked_name, sizeof(mocked_name), "%s_mock", __func__); + OstreeDeployment* (*mocked)(OstreeSysroot*) = dlsym(RTLD_DEFAULT, mocked_name); + + if (mocked) { + return mocked(self); + } + return orig(self); +} + +const char *ostree_deployment_get_bootcsum (OstreeDeployment *self) { + char* (*orig)(OstreeDeployment*) = dlsym(RTLD_NEXT, __func__); + char mocked_name[100]; + snprintf(mocked_name, sizeof(mocked_name), "%s_mock", __func__); + char* (*mocked)(OstreeDeployment*) = dlsym(RTLD_DEFAULT, mocked_name); + + if (mocked) { + return mocked(self); + } + return orig(self); +} From 168508b31743297afa55b5345c6e139184268f54 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Fri, 26 Apr 2019 14:50:23 +0200 Subject: [PATCH 7/9] Add complete cycle ostree install test With some parts mocked (emulate booted deployments) Signed-off-by: Laurent Bonnans --- src/aktualizr_repo/image_repo.cc | 2 +- src/aktualizr_repo/image_repo.h | 2 +- src/libaktualizr/primary/CMakeLists.txt | 9 + src/libaktualizr/primary/aktualizr.h | 1 + .../primary/aktualizr_fullostree_test.cc | 160 ++++++++++++++++++ src/libaktualizr/primary/sotauptaneclient.h | 1 + tests/fake_http_server/fake_http_server.py | 17 +- tests/fake_http_server/fake_test_server.py | 85 ++++++---- tests/ostree_mock.c | 4 +- tests/test_utils.cc | 13 +- 10 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 src/libaktualizr/primary/aktualizr_fullostree_test.cc diff --git a/src/aktualizr_repo/image_repo.cc b/src/aktualizr_repo/image_repo.cc index 466b79f943..3c3f548fcb 100644 --- a/src/aktualizr_repo/image_repo.cc +++ b/src/aktualizr_repo/image_repo.cc @@ -16,7 +16,7 @@ void ImageRepo::addImage(const std::string &name, const Json::Value &target, con } void ImageRepo::addBinaryImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, - const Delegation &delegation) { + const Delegation &delegation) { boost::filesystem::path repo_dir(path_ / "repo/image"); boost::filesystem::path targets_path = repo_dir / "targets"; diff --git a/src/aktualizr_repo/image_repo.h b/src/aktualizr_repo/image_repo.h index 0b6b6050fe..5c0d59dc60 100644 --- a/src/aktualizr_repo/image_repo.h +++ b/src/aktualizr_repo/image_repo.h @@ -8,7 +8,7 @@ class ImageRepo : public Repo { ImageRepo(boost::filesystem::path path, const std::string &expires, std::string correlation_id) : Repo(Uptane::RepositoryType::Image(), std::move(path), expires, std::move(correlation_id)) {} void addBinaryImage(const boost::filesystem::path &image_path, const boost::filesystem::path &targetname, - const Delegation &delegation = {}); + const Delegation &delegation = {}); void addCustomImage(const std::string &name, const Uptane::Hash &hash, uint64_t length, const Delegation &delegation, const Json::Value &custom = {}); void addDelegation(const Uptane::Role &name, const Uptane::Role &parent_role, const std::string &path, diff --git a/src/libaktualizr/primary/CMakeLists.txt b/src/libaktualizr/primary/CMakeLists.txt index 9dddf570e9..a3876385ba 100644 --- a/src/libaktualizr/primary/CMakeLists.txt +++ b/src/libaktualizr/primary/CMakeLists.txt @@ -16,6 +16,15 @@ add_library(primary OBJECT ${SOURCES}) add_aktualizr_test(NAME aktualizr SOURCES aktualizr_test.cc PROJECT_WORKING_DIRECTORY ARGS ${PROJECT_BINARY_DIR}/uptane_repos) add_dependencies(t_aktualizr uptane_repo_full_no_correlation_id) +if (BUILD_OSTREE) + add_aktualizr_test(NAME aktualizr_fullostree SOURCES aktualizr_fullostree_test.cc PROJECT_WORKING_DIRECTORY ARGS $ ${PROJECT_BINARY_DIR}/ostree_repo) + set_target_properties(t_aktualizr_fullostree PROPERTIES LINK_FLAGS -Wl,--export-dynamic) + add_dependencies(t_aktualizr_fullostree ostree_mock aktualizr-repo make_ostree_sysroot) + set_tests_properties(test_aktualizr_fullostree PROPERTIES ENVIRONMENT LD_PRELOAD=$) +else (BUILD_OSTREE) + aktualizr_source_file_checks(aktualizr_fullostree_test.cc) +endif (BUILD_OSTREE) + add_aktualizr_test(NAME reportqueue SOURCES reportqueue_test.cc PROJECT_WORKING_DIRECTORY) add_aktualizr_test(NAME emptytargets SOURCES empty_targets_test.cc PROJECT_WORKING_DIRECTORY ARGS "$") diff --git a/src/libaktualizr/primary/aktualizr.h b/src/libaktualizr/primary/aktualizr.h index a4711a8644..9c876e5d75 100644 --- a/src/libaktualizr/primary/aktualizr.h +++ b/src/libaktualizr/primary/aktualizr.h @@ -178,6 +178,7 @@ class Aktualizr { FRIEND_TEST(Aktualizr, AddSecondary); FRIEND_TEST(Aktualizr, EmptyTargets); FRIEND_TEST(Aktualizr, EmptyTargetsAfterInstall); + FRIEND_TEST(Aktualizr, FullOstreeUpdate); FRIEND_TEST(Delegation, Basic); FRIEND_TEST(Delegation, RevokeAfterCheckUpdates); FRIEND_TEST(Delegation, RevokeAfterInstall); diff --git a/src/libaktualizr/primary/aktualizr_fullostree_test.cc b/src/libaktualizr/primary/aktualizr_fullostree_test.cc new file mode 100644 index 0000000000..38eaf25d89 --- /dev/null +++ b/src/libaktualizr/primary/aktualizr_fullostree_test.cc @@ -0,0 +1,160 @@ +#include + +#include +#include +#include +#include + +#include + +#include "uptane_test_common.h" + +#include "config/config.h" +#include "logging/logging.h" +#include "package_manager/ostreemanager.h" +#include "primary/aktualizr.h" +#include "storage/sqlstorage.h" +#include "test_utils.h" + +boost::filesystem::path aktualizr_repo_path; +static std::string server = "http://127.0.0.1:"; +static std::string treehub_server = "http://127.0.0.1:"; +static boost::filesystem::path sysroot; + +static struct { + int serial{0}; + std::string rev; +} ostree_deployment; +static std::string new_rev; + +#include +extern "C" OstreeDeployment *ostree_sysroot_get_booted_deployment_mock(OstreeSysroot *self) { + (void)self; + static GObjectUniquePtr dep; + + dep.reset(ostree_deployment_new(0, "dummy-os", ostree_deployment.rev.c_str(), ostree_deployment.serial, + ostree_deployment.rev.c_str(), ostree_deployment.serial)); + return dep.get(); +} + +extern "C" const char *ostree_deployment_get_csum(OstreeDeployment *self) { + (void)self; + return ostree_deployment.rev.c_str(); +} + +TEST(Aktualizr, FullOstreeUpdate) { + TemporaryDirectory temp_dir; + Config conf = UptaneTestCommon::makeTestConfig(temp_dir, server); + conf.pacman.type = PackageManager::kOstree; + conf.pacman.sysroot = sysroot.string(); + conf.pacman.ostree_server = treehub_server; + conf.pacman.os = "dummy-os"; + conf.provision.device_id = "device_id"; + conf.provision.ecu_registration_endpoint = server + "/director/ecus"; + conf.tls.server = server; + + LOG_INFO << "conf: " << conf; + + { + Aktualizr aktualizr(conf); + + aktualizr.Initialize(); + + result::UpdateCheck update_result = aktualizr.CheckUpdates().get(); + ASSERT_EQ(update_result.status, result::UpdateStatus::kUpdatesAvailable); + + result::Download download_result = aktualizr.Download(update_result.updates).get(); + EXPECT_EQ(download_result.status, result::DownloadStatus::kSuccess); + + result::Install install_result = aktualizr.Install(update_result.updates).get(); + EXPECT_EQ(install_result.ecu_reports.size(), 1); + EXPECT_EQ(install_result.ecu_reports[0].install_res.result_code.num_code, + data::ResultCode::Numeric::kNeedCompletion); + } + + // do "reboot" and finalize + ostree_deployment.serial = 1; + ostree_deployment.rev = new_rev; + boost::filesystem::remove(conf.bootloader.reboot_sentinel_dir / conf.bootloader.reboot_sentinel_name); + + { + Aktualizr aktualizr(conf); + + aktualizr.Initialize(); + + result::UpdateCheck update_result = aktualizr.CheckUpdates().get(); + ASSERT_EQ(update_result.status, result::UpdateStatus::kNoUpdatesAvailable); + + // check new version + const auto target = aktualizr.uptane_client_->package_manager_->getCurrent(); + EXPECT_EQ(target.sha256Hash(), new_rev); + } +} + +#ifndef __NO_MAIN__ +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + + logger_init(); + + if (argc != 3) { + std::cerr << "Error: " << argv[0] << " requires the path to the aktualizr-repo utility " + << "and an OStree sysroot\n"; + return EXIT_FAILURE; + } + aktualizr_repo_path = argv[1]; + + Process ostree("ostree"); + + TemporaryDirectory meta_dir; + TemporaryDirectory temp_sysroot; + sysroot = temp_sysroot / "sysroot"; + // uses cp, as boost doesn't like to copy bad symlinks + int res = system((std::string("cp -r ") + argv[2] + std::string(" ") + sysroot.string()).c_str()); + if (res != 0) { + return -1; + } + auto r = ostree.run( + {"rev-parse", std::string("--repo"), (sysroot / "/ostree/repo").string(), "generate-remote/generated"}); + if (std::get<0>(r) != 0) { + return -1; + } + ostree_deployment.serial = 0; + ostree_deployment.rev = ostree.lastStdOut(); + boost::trim_if(ostree_deployment.rev, boost::is_any_of(" \t\r\n")); + LOG_INFO << "ORIG: " << ostree_deployment.rev; + + std::string port = TestUtils::getFreePort(); + server += port; + boost::process::child http_server_process("tests/fake_http_server/fake_test_server.py", port, "-m", meta_dir.Path()); + TestUtils::waitForServer(server + "/"); + + std::string treehub_port = TestUtils::getFreePort(); + treehub_server += treehub_port; + TemporaryDirectory treehub_dir; + boost::process::child ostree_server_process("tests/sota_tools/treehub_server.py", std::string("-p"), treehub_port, + std::string("-d"), treehub_dir.PathString(), std::string("-s0.5"), + std::string("--create")); + TestUtils::waitForServer(treehub_server + "/"); + r = ostree.run({"rev-parse", std::string("--repo"), treehub_dir.PathString(), "master"}); + if (std::get<0>(r) != 0) { + return -1; + } + new_rev = ostree.lastStdOut(); + boost::trim_if(new_rev, boost::is_any_of(" \t\r\n")); + LOG_INFO << "DEST: " << new_rev; + + Process akt_repo(aktualizr_repo_path.string()); + akt_repo.run({"generate", "--path", meta_dir.PathString(), "--correlationid", "abc123"}); + akt_repo.run({"image", "--path", meta_dir.PathString(), "--targetname", "update_1.0", "--targetsha256", new_rev, + "--targetlength", "0", "--targetformat", "OSTREE"}); + akt_repo.run({"addtarget", "--path", meta_dir.PathString(), "--targetname", "update_1.0", "--hwid", "primary_hw", + "--serial", "CA:FE:A6:D2:84:9D"}); + akt_repo.run({"signtargets", "--path", meta_dir.PathString(), "--correlationid", "abc123"}); + LOG_INFO << akt_repo.lastStdOut(); + // Work around inconsistent directory naming. + Utils::copyDir(meta_dir.Path() / "repo/image", meta_dir.Path() / "repo/repo"); + + return RUN_ALL_TESTS(); +} +#endif // __NO_MAIN__ diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h index 00f1dbde01..36fdc47977 100644 --- a/src/libaktualizr/primary/sotauptaneclient.h +++ b/src/libaktualizr/primary/sotauptaneclient.h @@ -73,6 +73,7 @@ class SotaUptaneClient { FRIEND_TEST(Aktualizr, InstallationFailure); FRIEND_TEST(Aktualizr, AutoRebootAfterUpdate); FRIEND_TEST(Aktualizr, EmptyTargets); + FRIEND_TEST(Aktualizr, FullOstreeUpdate); FRIEND_TEST(Uptane, AssembleManifestGood); FRIEND_TEST(Uptane, AssembleManifestBad); FRIEND_TEST(Uptane, InstallFake); diff --git a/tests/fake_http_server/fake_http_server.py b/tests/fake_http_server/fake_http_server.py index be82793ab8..23bd8a21a9 100755 --- a/tests/fake_http_server/fake_http_server.py +++ b/tests/fake_http_server/fake_http_server.py @@ -79,14 +79,27 @@ def do_POST(self): return else: last_fails = False - if self.path == '/token': + if self.path == '/devices': + self.send_response(200) + self.end_headers() + with open('tests/test_data/cred.p12', 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + elif self.path == '/token': self.send_response(200) self.end_headers() if 'Authorization' in self.headers: self.wfile.write(b"{\"access_token\": \"token\"}") else: self.wfile.write(b'') - + elif self.path == "/director/ecus": + self.send_response(200) + self.end_headers() + self.wfile.write(b"{}") + return else: self.send_response(200) self.end_headers() diff --git a/tests/fake_http_server/fake_test_server.py b/tests/fake_http_server/fake_test_server.py index 795c455dc4..861705989d 100755 --- a/tests/fake_http_server/fake_test_server.py +++ b/tests/fake_http_server/fake_test_server.py @@ -1,48 +1,55 @@ #!/usr/bin/python3 +import argparse import sys import socket + from http.server import SimpleHTTPRequestHandler, HTTPServer from time import sleep +file_path = None +meta_path = None + + class Handler(SimpleHTTPRequestHandler): + def _serve_simple(self, uri): + with open(uri, 'rb') as source: + while True: + data = source.read(1024) + if not data: + break + self.wfile.write(data) + + def serve_meta(self, uri): + if meta_path is None: + raise RuntimeError("Please supply a path for metadata") + self._serve_simple(meta_path + uri) + + def serve_target(self, filename): + if file_path is None: + raise RuntimeError("Please supply a path for targets") + self._serve_simple(file_path + filename) + def do_GET(self): + print("path: " + self.path) if self.path.startswith("/director/") and self.path.endswith(".json"): self.send_response(200) self.end_headers() role = self.path[len("/director/"):] - with open(meta_path + '/repo/director/' + role, 'rb') as source: - while True: - data = source.read(1024) - if not data: - break - self.wfile.write(data) - return + self.serve_meta("/repo/director/" + role) elif self.path.startswith("/repo/") and self.path.endswith(".json"): self.send_response(200) self.end_headers() role = self.path[len("/repo/"):] - with open(meta_path + '/repo/image/' + role, 'rb') as source: - while True: - data = source.read(1024) - if not data: - break - self.wfile.write(data) - return + self.serve_meta('/repo/image/' + role) elif self.path.startswith("/repo/targets"): self.send_response(200) self.end_headers() filename = self.path[len("/repo/targets"):] - with open(file_path + filename, 'rb') as source: - while True: - data = source.read(1024) - if not data: - break - self.wfile.write(data) - return + self.serve_target(filename) - if self.path == '/download': + elif self.path == '/download': self.send_response(301) self.send_header('Location', '/download/file') self.end_headers() @@ -85,12 +92,12 @@ def do_GET(self): self.send_response(200) self.end_headers() for i in range(5): - self.wfile.write(b'aa') - sleep(1) + self.wfile.write(b'aa') + sleep(1) else: self.send_response(200) self.end_headers() - self.wfile.write(b'{"path": "%b"}'%bytes(self.path, "utf8")) + self.wfile.write(b'{"path": "%b"}' % bytes(self.path, "utf8")) def do_POST(self): if self.path == '/devices': @@ -137,13 +144,25 @@ def server_bind(self): HTTPServer.server_bind(self) -server_address = ('', int(sys.argv[1])) -file_path = sys.argv[2] -meta_path = sys.argv[3] +def main(): + global file_path, meta_path + + parser = argparse.ArgumentParser(description='Run a fake OTA backend') + parser.add_argument('port', type=int, help='server port') + parser.add_argument('-t', '--targets', help='targets directory', default=None) + parser.add_argument('-m', '--meta', help='meta directory', default=None) + args = parser.parse_args() + + server_address = ('', args.port) + file_path = args.targets + meta_path = args.meta + + httpd = ReUseHTTPServer(server_address, Handler) + try: + httpd.serve_forever() + except KeyboardInterrupt: + httpd.server_close() -httpd = ReUseHTTPServer(server_address, Handler) -try: - httpd.serve_forever() -except KeyboardInterrupt: - httpd.server_close() +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/ostree_mock.c b/tests/ostree_mock.c index 46b7f8e60c..fe3c985396 100644 --- a/tests/ostree_mock.c +++ b/tests/ostree_mock.c @@ -3,7 +3,7 @@ #include #include -OstreeDeployment *ostree_sysroot_get_booted_deployment (OstreeSysroot *self) { +OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* self) { OstreeDeployment* (*orig)(OstreeSysroot*) = dlsym(RTLD_NEXT, __func__); char mocked_name[100]; snprintf(mocked_name, sizeof(mocked_name), "%s_mock", __func__); @@ -15,7 +15,7 @@ OstreeDeployment *ostree_sysroot_get_booted_deployment (OstreeSysroot *self) { return orig(self); } -const char *ostree_deployment_get_bootcsum (OstreeDeployment *self) { +const char* ostree_deployment_get_bootcsum(OstreeDeployment* self) { char* (*orig)(OstreeDeployment*) = dlsym(RTLD_NEXT, __func__); char mocked_name[100]; snprintf(mocked_name, sizeof(mocked_name), "%s_mock", __func__); diff --git a/tests/test_utils.cc b/tests/test_utils.cc index e9bd23d30d..abb59f97b4 100644 --- a/tests/test_utils.cc +++ b/tests/test_utils.cc @@ -16,6 +16,8 @@ #include #include +#include "logging/logging.h" + std::string TestUtils::getFreePort() { int s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { @@ -90,7 +92,13 @@ Process::Result Process::spawn(const std::string &executable_to_run, const std:: boost::asio::io_service io_service; try { - boost::process::child child_process(boost::process::exe = executable_to_run, boost::process::args = executable_args, + std::string executable_path; + if (boost::filesystem::exists(executable_to_run)) { + executable_path = executable_to_run; + } else { + executable_path = boost::process::search_path(executable_to_run).string(); + } + boost::process::child child_process(boost::process::exe = executable_path, boost::process::args = executable_args, boost::process::std_out > output, boost::process::std_err > err_output, boost::process::on_exit = child_process_exit_code, io_service); @@ -119,6 +127,9 @@ Process::Result Process::run(const std::vector &args) { auto cred_gen_result = Process::spawn(exe_path_, args); std::tie(last_exit_code_, last_stdout_, last_stderr_) = cred_gen_result; + if (last_exit_code_ != 0) { + LOG_WARNING << last_stderr_; + } return cred_gen_result; } From cb99aa68bb7ff991e671e9e7a02f77f0674090c4 Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Mon, 29 Apr 2019 11:27:20 +0200 Subject: [PATCH 8/9] Merge fake_http_server and fake_test_server Signed-off-by: Laurent Bonnans --- src/libaktualizr/http/httpclient_test.cc | 2 +- .../package_manager/fetcher_death_test.cc | 2 +- .../package_manager/fetcher_test.cc | 2 +- tests/fake_http_server/fake_http_server.py | 126 ------------------ tests/fake_http_server/fake_test_server.py | 38 +++++- 5 files changed, 35 insertions(+), 135 deletions(-) delete mode 100755 tests/fake_http_server/fake_http_server.py diff --git a/src/libaktualizr/http/httpclient_test.cc b/src/libaktualizr/http/httpclient_test.cc index 81b104541f..affa295fa0 100644 --- a/src/libaktualizr/http/httpclient_test.cc +++ b/src/libaktualizr/http/httpclient_test.cc @@ -80,7 +80,7 @@ int main(int argc, char** argv) { std::string port = TestUtils::getFreePort(); server += port; - boost::process::child server_process("tests/fake_http_server/fake_http_server.py", port); + boost::process::child server_process("tests/fake_http_server/fake_test_server.py", port, "-f"); TestUtils::waitForServer(server + "/"); return RUN_ALL_TESTS(); diff --git a/src/libaktualizr/package_manager/fetcher_death_test.cc b/src/libaktualizr/package_manager/fetcher_death_test.cc index 284e87285a..6e340d155f 100644 --- a/src/libaktualizr/package_manager/fetcher_death_test.cc +++ b/src/libaktualizr/package_manager/fetcher_death_test.cc @@ -104,7 +104,7 @@ int main(int argc, char** argv) { std::string port = TestUtils::getFreePort(); server = "http://127.0.0.1:" + port; config.uptane.repo_server = server; - boost::process::child http_server_process("tests/fake_http_server/fake_http_server.py", port); + boost::process::child http_server_process("tests/fake_http_server/fake_test_server.py", port, "-f"); TestUtils::waitForServer(server + "/"); return RUN_ALL_TESTS(); } diff --git a/src/libaktualizr/package_manager/fetcher_test.cc b/src/libaktualizr/package_manager/fetcher_test.cc index 0cdb5e1a09..bee46c5c24 100644 --- a/src/libaktualizr/package_manager/fetcher_test.cc +++ b/src/libaktualizr/package_manager/fetcher_test.cc @@ -233,7 +233,7 @@ int main(int argc, char** argv) { std::string port = TestUtils::getFreePort(); server += port; - boost::process::child http_server_process("tests/fake_http_server/fake_http_server.py", port); + boost::process::child http_server_process("tests/fake_http_server/fake_test_server.py", port, "-f"); TestUtils::waitForServer(server + "/"); #ifdef BUILD_OSTREE std::string treehub_port = TestUtils::getFreePort(); diff --git a/tests/fake_http_server/fake_http_server.py b/tests/fake_http_server/fake_http_server.py deleted file mode 100755 index 23bd8a21a9..0000000000 --- a/tests/fake_http_server/fake_http_server.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/python3 - -import sys -import socket -from http.server import SimpleHTTPRequestHandler, HTTPServer -from time import sleep - -last_fails = False - -class Handler(SimpleHTTPRequestHandler): - def do_GET(self): - global last_fails - if self.path == '/download': - self.send_response(301) - self.send_header('Location', '/download/file') - self.end_headers() - elif self.path == '/download/file': - self.send_response(200) - self.end_headers() - self.wfile.write(b'content') - elif self.path == '/auth_call': - self.send_response(200) - self.end_headers() - if 'Authorization' in self.headers: - auth_list = self.headers['Authorization'].split(' ') - if auth_list[0] == 'Bearer' and auth_list[1] == 'token': - self.wfile.write(b'{"status": "good"}') - self.wfile.write(b'{}') - elif self.path.endswith('/large_file'): - chunk_size = 1 << 20 - response_size = 100 * chunk_size - if "Range" in self.headers: - r = self.headers["Range"] - r_from = int(r.split("=")[1].split("-")[0]) - self.send_response(206) - self.send_header('Content-Range', 'bytes %d-%d/%d' %(r_from, response_size-1, response_size)) - response_size = response_size - r_from - else: - self.send_response(200) - self.send_header('Content-Type', 'application/json') - self.send_header('Content-Length', response_size) - self.end_headers() - num_chunks, last_chunk = divmod(response_size, chunk_size) - b = b'@' * chunk_size - try: - while num_chunks > 0: - self.wfile.write(b) - num_chunks -= 1 - self.wfile.write(b'@' * last_chunk) - except ConnectionResetError: - return - elif self.path == '/slow_file': - self.send_response(200) - self.end_headers() - for i in range(5): - self.wfile.write(b'aa') - sleep(1) - else: - if not last_fails: - self.send_response(503) - self.end_headers() - last_fails = True - self.wfile.write(b"Internal server error") - return - else: - last_fails = False - - self.send_response(200) - self.end_headers() - self.wfile.write(b'{"path": "%b"}'%bytes(self.path, "utf8")) - - def do_POST(self): - global last_fails - if not last_fails: - self.send_response(503) - self.end_headers() - last_fails = True - self.wfile.write(b"Internal server error") - return - else: - last_fails = False - if self.path == '/devices': - self.send_response(200) - self.end_headers() - with open('tests/test_data/cred.p12', 'rb') as source: - while True: - data = source.read(1024) - if not data: - break - self.wfile.write(data) - elif self.path == '/token': - self.send_response(200) - self.end_headers() - if 'Authorization' in self.headers: - self.wfile.write(b"{\"access_token\": \"token\"}") - else: - self.wfile.write(b'') - elif self.path == "/director/ecus": - self.send_response(200) - self.end_headers() - self.wfile.write(b"{}") - return - else: - self.send_response(200) - self.end_headers() - length = int(self.headers.get('content-length')) - result = b'{"data": %b, "path": "%b"}'%(self.rfile.read(length), bytes(self.path, "utf8")) - self.wfile.write(result) - - def do_PUT(self): - self.do_POST() - - -class ReUseHTTPServer(HTTPServer): - def server_bind(self): - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - HTTPServer.server_bind(self) - - -server_address = ('', int(sys.argv[1])) -httpd = ReUseHTTPServer(server_address, Handler) -try: - httpd.serve_forever() -except KeyboardInterrupt: - httpd.server_close() - diff --git a/tests/fake_http_server/fake_test_server.py b/tests/fake_http_server/fake_test_server.py index 861705989d..caee8501f5 100755 --- a/tests/fake_http_server/fake_test_server.py +++ b/tests/fake_http_server/fake_test_server.py @@ -9,6 +9,23 @@ file_path = None meta_path = None +fail_injector = None + + +class FailInjector: + def __init__(self): + self.last_fails = False + + def fail(self, http_handler): + if self.last_fails: + self.last_fails = False + return False + else: + http_handler.send_response(503) + http_handler.end_headers() + http_handler.wfile.write(b"Internal server error") + self.last_fails = True + return True class Handler(SimpleHTTPRequestHandler): @@ -31,8 +48,6 @@ def serve_target(self, filename): self._serve_simple(file_path + filename) def do_GET(self): - print("path: " + self.path) - if self.path.startswith("/director/") and self.path.endswith(".json"): self.send_response(200) self.end_headers() @@ -72,7 +87,7 @@ def do_GET(self): r = self.headers["Range"] r_from = int(r.split("=")[1].split("-")[0]) self.send_response(206) - self.send_header('Content-Range', 'bytes %d-%d/%d' %(r_from, response_size-1, response_size)) + self.send_header('Content-Range', 'bytes %d-%d/%d' % (r_from, response_size-1, response_size)) response_size = response_size - r_from else: self.send_response(200) @@ -95,11 +110,16 @@ def do_GET(self): self.wfile.write(b'aa') sleep(1) else: + if fail_injector is not None and fail_injector.fail(self): + return self.send_response(200) self.end_headers() self.wfile.write(b'{"path": "%b"}' % bytes(self.path, "utf8")) def do_POST(self): + if fail_injector is not None and fail_injector.fail(self): + return + if self.path == '/devices': self.send_response(200) self.end_headers() @@ -129,10 +149,13 @@ def do_POST(self): self.wfile.write(b"{\"access_token\": \"token\"}") else: self.wfile.write(b'') - else: - self.send_response(404) + # for httpclient_test + self.send_response(200) self.end_headers() + length = int(self.headers.get('content-length')) + result = b'{"data": %b, "path": "%b"}'%(self.rfile.read(length), bytes(self.path, "utf8")) + self.wfile.write(result) def do_PUT(self): self.do_POST() @@ -145,17 +168,20 @@ def server_bind(self): def main(): - global file_path, meta_path + global file_path, meta_path, fail_injector parser = argparse.ArgumentParser(description='Run a fake OTA backend') parser.add_argument('port', type=int, help='server port') parser.add_argument('-t', '--targets', help='targets directory', default=None) parser.add_argument('-m', '--meta', help='meta directory', default=None) + parser.add_argument('-f', '--fail', help='enable intermittent failure', action='store_true') args = parser.parse_args() server_address = ('', args.port) file_path = args.targets meta_path = args.meta + if args.fail: + fail_injector = FailInjector() httpd = ReUseHTTPServer(server_address, Handler) try: From cc9f67022a0867acefa8cd7d1d79df820c57776b Mon Sep 17 00:00:00 2001 From: Laurent Bonnans Date: Mon, 29 Apr 2019 16:56:59 +0200 Subject: [PATCH 9/9] Add OStree update on the primary as a checked action! Signed-off-by: Laurent Bonnans --- actions.md | 2 +- src/libaktualizr/primary/aktualizr_fullostree_test.cc | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actions.md b/actions.md index 99164ad015..25bbd2dbfa 100644 --- a/actions.md +++ b/actions.md @@ -137,7 +137,7 @@ These are the primary actions that a user of libaktualizr can perform through th - [x] Send EcuInstallationStartedReport to server for primary (uptane_test.cc, aktualizr_test.cc) - [x] Send an event report (see below) - [x] Install an update on the primary - - [ ] Install an OSTree update on the primary + - [x] Install an OSTree update on the primary (aktualizr_fullostree_test.cc) - [ ] Notify "reboot needed" after an OSTree update trigger - [x] Set new version to pending status after an OSTree update trigger (aktualizr_test.cc) - [x] Send EcuInstallationAppliedReport to server after an OSTree update trigger (aktualizr_test.cc) diff --git a/src/libaktualizr/primary/aktualizr_fullostree_test.cc b/src/libaktualizr/primary/aktualizr_fullostree_test.cc index 38eaf25d89..bcba49fa81 100644 --- a/src/libaktualizr/primary/aktualizr_fullostree_test.cc +++ b/src/libaktualizr/primary/aktualizr_fullostree_test.cc @@ -42,6 +42,9 @@ extern "C" const char *ostree_deployment_get_csum(OstreeDeployment *self) { return ostree_deployment.rev.c_str(); } +/* + * Install an OSTree update on the primary. + */ TEST(Aktualizr, FullOstreeUpdate) { TemporaryDirectory temp_dir; Config conf = UptaneTestCommon::makeTestConfig(temp_dir, server);