From 76481c5eac470653f914b1fa728111b8f18af442 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 31 Oct 2024 14:56:18 +0700 Subject: [PATCH] fix ci --- engine/cli/commands/engine_install_cmd.cc | 92 +++++++++++---- engine/cli/commands/engine_update_cmd.cc | 112 +++++++++++++++++++ engine/cli/commands/engine_update_cmd.h | 23 ++++ engine/controllers/engines.cc | 10 +- engine/controllers/engines.h | 56 ++++++---- engine/e2e-test/test_api_engine_install.py | 14 ++- engine/e2e-test/test_api_engine_uninstall.py | 44 +++++++- engine/e2e-test/test_api_engine_update.py | 43 +++++++ engine/e2e-test/test_api_model_start.py | 2 +- engine/e2e-test/test_api_model_stop.py | 5 +- engine/services/engine_service.cc | 66 +++++++---- engine/services/engine_service.h | 4 +- engine/test/components/test_event.cc | 11 +- engine/utils/config_yaml_utils.h | 7 +- engine/utils/curl_utils.h | 99 +++++++++++++++- engine/utils/github_release_utils.h | 6 + 16 files changed, 508 insertions(+), 86 deletions(-) create mode 100644 engine/cli/commands/engine_update_cmd.cc create mode 100644 engine/cli/commands/engine_update_cmd.h create mode 100644 engine/e2e-test/test_api_engine_update.py diff --git a/engine/cli/commands/engine_install_cmd.cc b/engine/cli/commands/engine_install_cmd.cc index 442e2397b..465bfcb31 100644 --- a/engine/cli/commands/engine_install_cmd.cc +++ b/engine/cli/commands/engine_install_cmd.cc @@ -1,8 +1,8 @@ #include "engine_install_cmd.h" #include #include "server_start_cmd.h" +#include "utils/cli_selection_utils.h" #include "utils/download_progress.h" -#include "utils/json_helper.h" #include "utils/logging_utils.h" namespace commands { @@ -38,27 +38,77 @@ bool EngineInstallCmd::Exec(const std::string& engine, [&dp, &engine] { return dp.Handle(engine); }); CLI_LOG("Validating download items, please wait..") - httplib::Client cli(host_ + ":" + std::to_string(port_)); - Json::Value json_data; - json_data["version"] = version.empty() ? "latest" : version; - auto data_str = json_data.toStyledString(); - cli.set_read_timeout(std::chrono::seconds(60)); - auto res = cli.Post("/v1/engines/install/" + engine, httplib::Headers(), - data_str.data(), data_str.size(), "application/json"); + auto versions_url = url_parser::Url{ + .protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = {"v1", "engines", engine, "versions"}, + }; + auto versions_result = curl_utils::SimpleGetJson(versions_url.ToFullPath()); + if (versions_result.has_error()) { + CTL_ERR(versions_result.error()); + return false; + } + std::vector version_selections; + for (const auto& release_version : versions_result.value()) { + version_selections.push_back(release_version["name"].asString()); + } - if (res) { - if (res->status != httplib::StatusCode::OK_200) { - auto root = json_helper::ParseJsonString(res->body); - CLI_LOG(root["message"].asString()); - dp.ForceStop(); - return false; - } else { - CLI_LOG("Start downloading.."); - } - } else { - auto err = res.error(); - CTL_ERR("HTTP error: " << httplib::to_string(err)); - dp.ForceStop(); + auto selected_version = + cli_selection_utils::PrintSelection(version_selections); + if (selected_version == std::nullopt) { + CTL_ERR("Invalid version selection"); + return false; + } + std::cout << "Selected version: " << selected_version.value() << std::endl; + + auto variant_url = url_parser::Url{ + .protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = + { + "v1", + "engines", + engine, + "versions", + selected_version.value(), + }, + }; + auto variant_result = curl_utils::SimpleGetJson(variant_url.ToFullPath()); + if (variant_result.has_error()) { + CTL_ERR(variant_result.error()); + return false; + } + + std::vector variant_selections; + for (const auto& variant : variant_result.value()) { + variant_selections.push_back(variant["name"].asString()); + } + auto selected_variant = + cli_selection_utils::PrintSelection(variant_selections); + if (selected_variant == std::nullopt) { + CTL_ERR("Invalid variant selection"); + return false; + } + std::cout << "Selected " << selected_variant.value() << " - " + << selected_version.value() << std::endl; + + auto install_url = + url_parser::Url{.protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = + { + "v1", + "engines", + engine, + }, + .queries = { + {"version", selected_version.value()}, + {"variant", selected_variant.value()}, + }}; + + auto response = curl_utils::SimplePostJson(install_url.ToFullPath()); + if (response.has_error()) { + CTL_ERR(response.error()); return false; } diff --git a/engine/cli/commands/engine_update_cmd.cc b/engine/cli/commands/engine_update_cmd.cc new file mode 100644 index 000000000..8542d7644 --- /dev/null +++ b/engine/cli/commands/engine_update_cmd.cc @@ -0,0 +1,112 @@ +#include "engine_update_cmd.h" +#include +#include "server_start_cmd.h" +#include "utils/cli_selection_utils.h" +#include "utils/download_progress.h" +#include "utils/logging_utils.h" + +namespace commands { +bool EngineUpdateCmd::Exec(const std::string& engine) { + // Start server if server is not started yet + if (!commands::IsServerAlive(host_, port_)) { + CLI_LOG("Starting server ..."); + commands::ServerStartCmd ssc; + if (!ssc.Exec(host_, port_)) { + return false; + } + } + // TODO: implement this + DownloadProgress dp; + dp.Connect(host_, port_); + // engine can be small, so need to start ws first + auto dp_res = std::async(std::launch::deferred, + [&dp, &engine] { return dp.Handle(engine); }); + CLI_LOG("Validating download items, please wait..") + + auto versions_url = url_parser::Url{ + .protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = {"v1", "engines", engine, "versions"}, + }; + auto versions_result = curl_utils::SimpleGetJson(versions_url.ToFullPath()); + if (versions_result.has_error()) { + CTL_ERR(versions_result.error()); + return false; + } + std::vector version_selections; + for (const auto& release_version : versions_result.value()) { + version_selections.push_back(release_version["name"].asString()); + } + + auto selected_version = + cli_selection_utils::PrintSelection(version_selections); + if (selected_version == std::nullopt) { + CTL_ERR("Invalid version selection"); + return false; + } + std::cout << "Selected version: " << selected_version.value() << std::endl; + + auto variant_url = url_parser::Url{ + .protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = + { + "v1", + "engines", + engine, + "versions", + selected_version.value(), + }, + }; + auto variant_result = curl_utils::SimpleGetJson(variant_url.ToFullPath()); + if (variant_result.has_error()) { + CTL_ERR(variant_result.error()); + return false; + } + + std::vector variant_selections; + for (const auto& variant : variant_result.value()) { + variant_selections.push_back(variant["name"].asString()); + } + auto selected_variant = + cli_selection_utils::PrintSelection(variant_selections); + if (selected_variant == std::nullopt) { + CTL_ERR("Invalid variant selection"); + return false; + } + std::cout << "Selected " << selected_variant.value() << " - " + << selected_version.value() << std::endl; + + auto install_url = + url_parser::Url{.protocol = "http", + .host = host_ + ":" + std::to_string(port_), + .pathParams = + { + "v1", + "engines", + engine, + }, + .queries = { + {"version", selected_version.value()}, + {"variant", selected_variant.value()}, + }}; + + auto response = curl_utils::SimplePostJson(install_url.ToFullPath()); + if (response.has_error()) { + CTL_ERR(response.error()); + return false; + } + + if (!dp_res.get()) + return false; + + bool check_cuda_download = !system_info_utils::GetCudaVersion().empty(); + if (check_cuda_download) { + if (!dp.Handle("cuda")) + return false; + } + + CLI_LOG("Engine " << engine << " downloaded successfully!") + return true; +} +}; // namespace commands diff --git a/engine/cli/commands/engine_update_cmd.h b/engine/cli/commands/engine_update_cmd.h new file mode 100644 index 000000000..18d315f5c --- /dev/null +++ b/engine/cli/commands/engine_update_cmd.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "services/engine_service.h" + +namespace commands { + +class EngineUpdateCmd { + public: + explicit EngineUpdateCmd(std::shared_ptr download_service, + const std::string& host, int port) + : engine_service_{EngineService(download_service)}, + host_(host), + port_(port) {}; + + bool Exec(const std::string& engine); + + private: + EngineService engine_service_; + std::string host_; + int port_; +}; +} // namespace commands diff --git a/engine/controllers/engines.cc b/engine/controllers/engines.cc index 18110368a..9673ef29a 100644 --- a/engine/controllers/engines.cc +++ b/engine/controllers/engines.cc @@ -44,11 +44,11 @@ void Engines::ListEngine( void Engines::UninstallEngine( const HttpRequestPtr& req, std::function&& callback, - const std::string& engine, const std::string& version, - const std::string& variant) { + const std::string& engine, const std::optional version, + const std::optional variant) { auto result = - engine_service_->UninstallEngineVariant(engine, variant, version); + engine_service_->UninstallEngineVariant(engine, version, variant); Json::Value ret; if (result.has_error()) { @@ -128,7 +128,7 @@ void Engines::GetEngineVariants( callback(resp); } -void Engines::InstallEngineVariant( +void Engines::InstallEngine( const HttpRequestPtr& req, std::function&& callback, const std::string& engine, const std::optional version, @@ -152,7 +152,7 @@ void Engines::InstallEngineVariant( } } -void Engines::GetEnginesInstalledVariants( +void Engines::GetInstalledEngineVariants( const HttpRequestPtr& req, std::function&& callback, const std::string& engine) const { diff --git a/engine/controllers/engines.h b/engine/controllers/engines.h index 49a8603eb..edeadc698 100644 --- a/engine/controllers/engines.h +++ b/engine/controllers/engines.h @@ -12,25 +12,36 @@ class Engines : public drogon::HttpController { public: METHOD_LIST_BEGIN - METHOD_ADD(Engines::InstallEngineVariant, "/{1}?version={2}&variant={3}", - Post); - METHOD_ADD(Engines::UninstallEngine, "/{1}/{2}/{3}", Delete); - METHOD_ADD(Engines::ListEngine, "", Get); - - METHOD_ADD(Engines::GetEngineVersions, "/{1}/versions", Get); - METHOD_ADD(Engines::GetEngineVariants, "/{1}/versions/{2}", Get); - METHOD_ADD(Engines::InstallEngineVariant, "/{1}/versions/{2}/{3}", Post); - METHOD_ADD(Engines::GetEnginesInstalledVariants, "/{1}", Get); - - // METHOD_ADD(Engines::GetLatestEngineVersion, "/{1}/update", Get); - METHOD_ADD(Engines::UpdateEngine, "/{1}/update", Post); - METHOD_ADD(Engines::SetDefaultEngineVariant, "/{1}/default/{2}/{3}", Post); + METHOD_ADD(Engines::GetInstalledEngineVariants, "/{1}", Get); + METHOD_ADD(Engines::InstallEngine, "/{1}?version={2}&variant={3}", Post); + METHOD_ADD(Engines::UninstallEngine, "/{1}?version={2}&variant={3}", Delete); + METHOD_ADD(Engines::SetDefaultEngineVariant, + "/{1}/default?version={2}&variant={3}", Post); METHOD_ADD(Engines::GetDefaultEngineVariant, "/{1}/default", Get); METHOD_ADD(Engines::LoadEngine, "/{1}/load", Post); METHOD_ADD(Engines::UnloadEngine, "/{1}/load", Delete); + METHOD_ADD(Engines::UpdateEngine, "/{1}/update", Post); + METHOD_ADD(Engines::ListEngine, "", Get); + METHOD_ADD(Engines::GetEngineVersions, "/{1}/versions", Get); + METHOD_ADD(Engines::GetEngineVariants, "/{1}/versions/{2}", Get); - ADD_METHOD_TO(Engines::UninstallEngine, "/v1/engines/{1}/{2}/{3}", Delete); + ADD_METHOD_TO(Engines::GetInstalledEngineVariants, "/v1/engines/{1}", Get); + ADD_METHOD_TO(Engines::InstallEngine, + "/v1/engines/{1}?version={2}&variant={3}", Post); + ADD_METHOD_TO(Engines::UninstallEngine, + "/v1/engines/{1}?version={2}&variant={3}", Delete); + ADD_METHOD_TO(Engines::SetDefaultEngineVariant, + "/v1/engines/{1}/default?version={2}&variant={3}", Post); + ADD_METHOD_TO(Engines::GetDefaultEngineVariant, "/v1/engines/{1}/default", + Get); + + ADD_METHOD_TO(Engines::LoadEngine, "/v1/engines/{1}/load", Post); + ADD_METHOD_TO(Engines::UnloadEngine, "/v1/engines/{1}/load", Post); + ADD_METHOD_TO(Engines::UpdateEngine, "/v1/engines/{1}/update", Post); + ADD_METHOD_TO(Engines::GetEngineVersions, "/v1/engines/{1}/versions", Get); + ADD_METHOD_TO(Engines::GetEngineVariants, "/v1/engines/{1}/versions/{2}", + Get); METHOD_LIST_END @@ -42,8 +53,9 @@ class Engines : public drogon::HttpController { void UninstallEngine(const HttpRequestPtr& req, std::function&& callback, - const std::string& engine, const std::string& version, - const std::string& variant); + const std::string& engine, + const std::optional version, + const std::optional variant); void GetEngineVersions(const HttpRequestPtr& req, std::function&& callback, @@ -54,13 +66,13 @@ class Engines : public drogon::HttpController { const std::string& engine, const std::string& version) const; - void InstallEngineVariant( - const HttpRequestPtr& req, - std::function&& callback, - const std::string& engine, const std::optional version, - const std::optional variant_name); + void InstallEngine(const HttpRequestPtr& req, + std::function&& callback, + const std::string& engine, + const std::optional version, + const std::optional variant_name); - void GetEnginesInstalledVariants( + void GetInstalledEngineVariants( const HttpRequestPtr& req, std::function&& callback, const std::string& engine) const; diff --git a/engine/e2e-test/test_api_engine_install.py b/engine/e2e-test/test_api_engine_install.py index 749b45dd3..b0fbb6c9c 100644 --- a/engine/e2e-test/test_api_engine_install.py +++ b/engine/e2e-test/test_api_engine_install.py @@ -18,5 +18,17 @@ def setup_and_teardown(self): stop_server() def test_engines_install_llamacpp_should_be_successful(self): - response = requests.post("http://localhost:3928/engines/install/llama-cpp") + response = requests.post("http://localhost:3928/v1/engines/llama-cpp") + assert response.status_code == 200 + + def test_engines_install_llamacpp_specific_version_and_variant(self): + response = requests.post( + "http://localhost:3928/v1/engines/llama-cpp?version=v0.1.35-27.10.24&variant=linux-amd64-avx-cuda-11-7" + ) + assert response.status_code == 200 + + def test_engines_install_llamacpp_specific_version_and_null_variant(self): + response = requests.post( + "http://localhost:3928/v1/engines/llama-cpp?version=v0.1.35-27.10.24" + ) assert response.status_code == 200 diff --git a/engine/e2e-test/test_api_engine_uninstall.py b/engine/e2e-test/test_api_engine_uninstall.py index c171be8ee..491bc2d27 100644 --- a/engine/e2e-test/test_api_engine_uninstall.py +++ b/engine/e2e-test/test_api_engine_uninstall.py @@ -18,5 +18,47 @@ def setup_and_teardown(self): stop_server() def test_engines_uninstall_llamacpp_should_be_successful(self): - response = requests.delete("http://localhost:3928/engines/llama-cpp") + # install first + requests.post("http://localhost:3928/v1/engines/llama-cpp") + + response = requests.delete("http://localhost:3928/v1/engines/llama-cpp") + assert response.status_code == 200 + + def test_engines_uninstall_llamacpp_with_only_version_should_be_failed(self): + # install first + install_response = requests.post( + "http://localhost:3928/v1/engines/llama-cpp?version=v0.1.35" + ) + assert install_response.status_code == 200 + + response = requests.delete( + "http://localhost:3928/v1/engines/llama-cpp?version=v0.1.35" + ) + assert response.status_code == 400 + assert response.json()["message"] == "No variant provided" + + def test_engines_uninstall_llamacpp_with_variant_should_be_successful(self): + # install first + install_response = requests.post( + "http://localhost:3928/v1/engines/llama-cpp?variant=mac-arm64" + ) + assert install_response.status_code == 200 + + response = requests.delete( + "http://localhost:3928/v1/engines/llama-cpp?variant=mac-arm64" + ) + assert response.status_code == 200 + + def test_engines_uninstall_llamacpp_with_specific_variant_and_version_should_be_successful( + self, + ): + # install first + install_response = requests.post( + "http://localhost:3928/v1/engines/llama-cpp?variant=mac-arm64&version=v0.1.35" + ) + assert install_response.status_code == 200 + + response = requests.delete( + "http://localhost:3928/v1/engines/llama-cpp?variant=mac-arm64&version=v0.1.35" + ) assert response.status_code == 200 diff --git a/engine/e2e-test/test_api_engine_update.py b/engine/e2e-test/test_api_engine_update.py new file mode 100644 index 000000000..23939f038 --- /dev/null +++ b/engine/e2e-test/test_api_engine_update.py @@ -0,0 +1,43 @@ +import pytest +import requests +from test_runner import ( + start_server, + stop_server, + wait_for_websocket_download_success_event, +) + + +class TestApiEngineUpdate: + + @pytest.fixture(autouse=True) + def setup_and_teardown(self): + # Setup + success = start_server() + if not success: + raise Exception("Failed to start server") + requests.delete("http://localhost:3928/v1/engines/llama-cpp") + + yield + requests.delete("http://localhost:3928/v1/engines/llama-cpp") + + # Teardown + stop_server() + + @pytest.mark.asyncio + async def test_engines_update_should_be_successfully(self): + requests.post("http://localhost:3928/v1/engines/llama-cpp?version=0.1.34") + response = requests.post("http://localhost:3928/v1/engines/llama-cpp/update") + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_engines_update_llamacpp_should_be_failed_if_already_latest(self): + requests.post("http://localhost:3928/v1/engines/llama-cpp") + await wait_for_websocket_download_success_event(timeout=None) + get_engine_response = requests.get("http://localhost:3928/v1/engines/llama-cpp") + assert len(get_engine_response.json()) > 0, "Response list should not be empty" + + response = requests.post("http://localhost:3928/v1/engines/llama-cpp/update") + assert ( + "already up-to-date" in response.json()["message"] + ), "Should display error message" + assert response.status_code == 400 diff --git a/engine/e2e-test/test_api_model_start.py b/engine/e2e-test/test_api_model_start.py index 216fad570..3f4172d68 100644 --- a/engine/e2e-test/test_api_model_start.py +++ b/engine/e2e-test/test_api_model_start.py @@ -11,7 +11,7 @@ def setup_and_teardown(self): success = start_server() if not success: raise Exception("Failed to start server") - run("Install Engine", ["engines", "install", "llama-cpp"], timeout=None) + requests.post("http://localhost:3928/v1/engines/llama-cpp") run("Delete model", ["models", "delete", "tinyllama:gguf"]) run( "Pull model", diff --git a/engine/e2e-test/test_api_model_stop.py b/engine/e2e-test/test_api_model_stop.py index 00d7482fa..218331b98 100644 --- a/engine/e2e-test/test_api_model_stop.py +++ b/engine/e2e-test/test_api_model_stop.py @@ -1,6 +1,6 @@ import pytest import requests -from test_runner import run, start_server, stop_server +from test_runner import start_server, stop_server class TestApiModelStop: @@ -12,9 +12,10 @@ def setup_and_teardown(self): if not success: raise Exception("Failed to start server") - run("Install Engine", ["engines", "install", "llama-cpp"], timeout=None) + requests.post("http://localhost:3928/engines/llama-cpp") yield + requests.delete("http://localhost:3928/engines/llama-cpp") # Teardown stop_server() diff --git a/engine/services/engine_service.cc b/engine/services/engine_service.cc index 64887a142..64eac759e 100644 --- a/engine/services/engine_service.cc +++ b/engine/services/engine_service.cc @@ -170,17 +170,35 @@ cpp::result EngineService::UnzipEngine( } cpp::result EngineService::UninstallEngineVariant( - const std::string& engine, const std::string& variant, - const std::string& version) { + const std::string& engine, const std::optional version, + const std::optional variant) { auto ne = NormalizeEngine(engine); - auto engine_path = - file_manager_utils::GetEnginesContainerPath() / ne / variant / version; - if (!std::filesystem::exists(engine_path)) { - return cpp::fail("Engine " + ne + " is not installed!"); + + std::optional path_to_remove = std::nullopt; + if (version == std::nullopt && variant == std::nullopt) { + // if no version and variant provided, remove all engines variant of that engine + path_to_remove = file_manager_utils::GetEnginesContainerPath() / ne; + } else if (version != std::nullopt && variant != std::nullopt) { + // if both version and variant are provided, we only remove that variant + path_to_remove = file_manager_utils::GetEnginesContainerPath() / ne / + variant.value() / version.value(); + } else if (version == std::nullopt) { + // if only have variant, we remove all of that variant + path_to_remove = + file_manager_utils::GetEnginesContainerPath() / ne / variant.value(); + } else { + return cpp::fail("No variant provided"); + } + + if (path_to_remove == std::nullopt) { + return cpp::fail("Uninstall engine variant failed!"); + } + if (!std::filesystem::exists(path_to_remove.value())) { + return cpp::fail("Engine variant does not exist!"); } try { - std::filesystem::remove_all(engine_path); + std::filesystem::remove_all(path_to_remove.value()); CTL_INF("Engine " << ne << " uninstalled successfully!"); return true; } catch (const std::exception& e) { @@ -208,7 +226,10 @@ cpp::result EngineService::DownloadEngineV2( std::optional selected_variant = std::nullopt; if (variant_name.has_value()) { - auto merged_variant_name = engine + "-" + normalized_version + "-" + + auto latest_version_semantic = normalized_version == "latest" + ? res.value()[0].version + : normalized_version; + auto merged_variant_name = engine + "-" + latest_version_semantic + "-" + variant_name.value() + ".tar.gz"; for (const auto& asset : res.value()) { @@ -739,15 +760,14 @@ cpp::result EngineService::LoadEngine( auto& en = std::get(engines_[ne].engine); if (ne == kLlamaRepo) { //fix for llamacpp engine first auto config = file_manager_utils::GetCortexConfig(); - // TODO: crash issue with trantor logging destructor. - // if (en->IsSupported("SetFileLogger")) { - // en->SetFileLogger(config.maxLogLines, - // (std::filesystem::path(config.logFolderPath) / - // std::filesystem::path(config.logLlamaCppPath)) - // .string()); - // } else { - // LOG_WARN << "Method SetFileLogger is not supported yet"; - // } + if (en->IsSupported("SetFileLogger")) { + en->SetFileLogger(config.maxLogLines, + (std::filesystem::path(config.logFolderPath) / + std::filesystem::path(config.logLlamaCppPath)) + .string()); + } else { + LOG_WARN << "Method SetFileLogger is not supported yet"; + } } LOG_INFO << "Loaded engine: " << ne; return {}; @@ -833,17 +853,19 @@ cpp::result EngineService::UpdateEngine( // check if local engines variants if latest version already exist auto installed_variants = GetInstalledEngineVariants(ne); - bool is_installed = false; + bool latest_version_installed = false; for (const auto& v : installed_variants) { CTL_INF("Installed version: " + v.version); + CTL_INF(json_helper::DumpJsonString(v.ToJson())); if (default_variant->variant == v.name && - v.version == latest_version.value().name) { - is_installed = true; + string_utils::RemoveSubstring(v.version, "v") == + latest_version.value().name) { + latest_version_installed = true; break; } } - if (is_installed) { + if (latest_version_installed) { CTL_INF("Engine " + ne + ", " + default_variant->variant + " is already up-to-date! Version " + latest_version.value().tag_name); @@ -862,5 +884,5 @@ cpp::result EngineService::UpdateEngine( return EngineUpdateResult{.engine = engine, .variant = default_variant->variant, .from = default_variant->version, - .to = latest_version->name}; + .to = latest_version->tag_name}; } diff --git a/engine/services/engine_service.h b/engine/services/engine_service.h index 0487d3605..d10f29678 100644 --- a/engine/services/engine_service.h +++ b/engine/services/engine_service.h @@ -112,8 +112,8 @@ class EngineService { const std::optional variant_name); cpp::result UninstallEngineVariant( - const std::string& engine, const std::string& variant, - const std::string& version); + const std::string& engine, const std::optional version, + const std::optional variant); cpp::result, std::string> GetEngineReleases( const std::string& engine) const; diff --git a/engine/test/components/test_event.cc b/engine/test/components/test_event.cc index d10933f52..baa5fd16b 100644 --- a/engine/test/components/test_event.cc +++ b/engine/test/components/test_event.cc @@ -36,15 +36,14 @@ TEST_F(EventTest, EventFromString) { })"; // clang-format on auto root = json_helper::ParseJsonString(ev_str); - std::cout << root.toStyledString() << std::endl; - auto download_item = common::GetDownloadItemFromJson(root["task"]["items"][0]); - EXPECT_EQ(download_item.downloadUrl, root["task"]["items"][0]["downloadUrl"].asString()); - std::cout << download_item.ToString() << std::endl; + auto download_item = + common::GetDownloadItemFromJson(root["task"]["items"][0]); + EXPECT_EQ(download_item.downloadUrl, + root["task"]["items"][0]["downloadUrl"].asString()); auto download_task = common::GetDownloadTaskFromJson(root["task"]); - std::cout << download_task.ToString() << std::endl; auto ev = cortex::event::GetDownloadEventFromJson(root); EXPECT_EQ(ev.type_, cortex::event::DownloadEventType::DownloadStarted); -} \ No newline at end of file +} diff --git a/engine/utils/config_yaml_utils.h b/engine/utils/config_yaml_utils.h index cb7eef510..87a114d25 100644 --- a/engine/utils/config_yaml_utils.h +++ b/engine/utils/config_yaml_utils.h @@ -26,6 +26,7 @@ struct CortexConfig { * Github's API requires a user-agent string. */ std::string gitHubUserAgent; + std::string gitHubToken; std::string llamacppVariant; std::string llamacppVersion; }; @@ -58,6 +59,7 @@ inline cpp::result DumpYamlConfig(const CortexConfig& config, node["latestRelease"] = config.latestRelease; node["huggingFaceToken"] = config.huggingFaceToken; node["gitHubUserAgent"] = config.gitHubUserAgent; + node["gitHubToken"] = config.gitHubToken; node["llamacppVariant"] = config.llamacppVariant; node["llamacppVersion"] = config.llamacppVersion; @@ -86,7 +88,8 @@ inline CortexConfig FromYaml(const std::string& path, !node["latestRelease"] || !node["logLlamaCppPath"] || !node["logOnnxPath"] || !node["logTensorrtLLMPath"] || !node["huggingFaceToken"] || !node["gitHubUserAgent"] || - !node["llamacppVariant"] || !node["llamacppVersion"]); + !node["gitHubToken"] || !node["llamacppVariant"] || + !node["llamacppVersion"]); CortexConfig config = { .logFolderPath = node["logFolderPath"] @@ -124,6 +127,8 @@ inline CortexConfig FromYaml(const std::string& path, .gitHubUserAgent = node["gitHubUserAgent"] ? node["gitHubUserAgent"].as() : "", + .gitHubToken = + node["gitHubToken"] ? node["gitHubToken"].as() : "", .llamacppVariant = node["llamacppVariant"] ? node["llamacppVariant"].as() : "", diff --git a/engine/utils/curl_utils.h b/engine/utils/curl_utils.h index 9bffe2685..07c2a5670 100644 --- a/engine/utils/curl_utils.h +++ b/engine/utils/curl_utils.h @@ -8,6 +8,7 @@ #include #include "utils/engine_constants.h" #include "utils/file_manager_utils.h" +#include "utils/logging_utils.h" #include "utils/result.hpp" #include "utils/url_parser.h" @@ -34,9 +35,8 @@ inline cpp::result SimpleGet(const std::string& url) { } auto headers = GetHeaders(url); + curl_slist* curl_headers = nullptr; if (headers.has_value()) { - curl_slist* curl_headers = nullptr; - for (const auto& [key, value] : headers.value()) { auto header = key + ": " + value; curl_headers = curl_slist_append(curl_headers, header.c_str()); @@ -54,12 +54,73 @@ inline cpp::result SimpleGet(const std::string& url) { // Perform the request auto res = curl_easy_perform(curl); + curl_slist_free_all(curl_headers); + curl_easy_cleanup(curl); if (res != CURLE_OK) { return cpp::fail("CURL request failed: " + static_cast(curl_easy_strerror(res))); } + auto http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code >= 400) { + CTL_ERR("HTTP request failed with status code: " + + std::to_string(http_code)); + return cpp::fail("API request failed: " + + static_cast(curl_easy_strerror(res))); + } + + return readBuffer; +} + +inline cpp::result SimplePost( + const std::string& url, const std::string& body = "") { + curl_global_init(CURL_GLOBAL_DEFAULT); + auto curl = curl_easy_init(); + + if (!curl) { + return cpp::fail("Failed to init CURL"); + } + + auto headers = GetHeaders(url); + curl_slist* curl_headers = nullptr; + if (headers.has_value()) { + + for (const auto& [key, value] : headers.value()) { + auto header = key + ": " + value; + curl_headers = curl_slist_append(curl_headers, header.c_str()); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_headers); + } + + std::string readBuffer; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + // Perform the request + auto res = curl_easy_perform(curl); + + curl_slist_free_all(curl_headers); curl_easy_cleanup(curl); + if (res != CURLE_OK) { + CTL_ERR("CURL request failed: " + std::string(curl_easy_strerror(res))); + return cpp::fail("CURL request failed: " + + static_cast(curl_easy_strerror(res))); + } + auto http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code >= 400) { + CTL_ERR("HTTP request failed with status code: " + + std::to_string(http_code)); + return cpp::fail("API request failed: " + + static_cast(curl_easy_strerror(res))); + } + return readBuffer; } @@ -67,6 +128,7 @@ inline cpp::result ReadRemoteYaml( const std::string& url) { auto result = SimpleGet(url); if (result.has_error()) { + CTL_ERR("Failed to get Yaml from " + url + ": " + result.error()); return cpp::fail(result.error()); } @@ -82,9 +144,29 @@ inline cpp::result SimpleGetJson( const std::string& url) { auto result = SimpleGet(url); if (result.has_error()) { + CTL_ERR("Failed to get JSON from " + url + ": " + result.error()); + return cpp::fail(result.error()); + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(result.value(), root)) { + return cpp::fail("JSON from " + url + + " parsing error: " + reader.getFormattedErrorMessages()); + } + + return root; +} + +inline cpp::result SimplePostJson( + const std::string& url, const std::string& body = "") { + auto result = SimplePost(url, body); + if (result.has_error()) { + CTL_ERR("Failed to get JSON from " + url + ": " + result.error()); return cpp::fail(result.error()); } + CTL_INF("Response: " + result.value()); Json::Value root; Json::Reader reader; if (!reader.parse(result.value(), root)) { @@ -127,8 +209,21 @@ inline std::optional> GetHeaders( headers["Accept"] = "application/vnd.github.v3+json"; // github API requires user-agent https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent auto user_agent = file_manager_utils::GetCortexConfig().gitHubUserAgent; + auto gh_token = file_manager_utils::GetCortexConfig().gitHubToken; headers["User-Agent"] = user_agent.empty() ? kDefaultGHUserAgent : user_agent; + if (!gh_token.empty()) { + headers["Authorization"] = "Bearer " + gh_token; + + // for debug purpose + auto min_token_size = 6; + if (gh_token.size() < min_token_size) { + CTL_WRN("Github token is too short"); + } else { + CTL_INF("Using authentication with Github token: " + + gh_token.substr(gh_token.size() - min_token_size)); + } + } return headers; } diff --git a/engine/utils/github_release_utils.h b/engine/utils/github_release_utils.h index b7cbe419e..1081daa40 100644 --- a/engine/utils/github_release_utils.h +++ b/engine/utils/github_release_utils.h @@ -178,6 +178,11 @@ inline cpp::result GetReleaseByVersion( std::vector path_params{"repos", author, repo, "releases"}; if (tag != "latest") { path_params.push_back("tags"); + + if (!string_utils::StartsWith(tag, "v")) { + path_params.push_back("v" + tag); + } + path_params.push_back(tag); } else { path_params.push_back("latest"); @@ -189,6 +194,7 @@ inline cpp::result GetReleaseByVersion( .pathParams = path_params, }; + CTL_INF("GetReleaseByVersion: " << url.ToFullPath()); auto result = curl_utils::SimpleGetJson(url_parser::FromUrl(url)); if (result.has_error()) {