diff --git a/helio b/helio index 12d2da75afbc..af5e0533c1ad 160000 --- a/helio +++ b/helio @@ -1 +1 @@ -Subproject commit 12d2da75afbcfc8bbb5693180a6135abb8ba8d54 +Subproject commit af5e0533c1adfb45f5fa3e6c93f5dfa1fde98360 diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index ffd6ad947a99..b5cd208727b1 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -21,7 +21,7 @@ add_library(dragonfly_lib channel_slice.cc command_registry.cc zset_family.cc version.cc bitops_family.cc container_utils.cc serializer_commons.cc journal/serializer.cc journal/executor.cc) -cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib +cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib strings_lib html_lib http_client_lib absl::random_random TRDP::jsoncons zstd TRDP::lz4) add_library(dfly_test_lib test_utils.cc) diff --git a/src/server/dfly_main.cc b/src/server/dfly_main.cc index 09bab7c59e26..6cfc5461e75a 100644 --- a/src/server/dfly_main.cc +++ b/src/server/dfly_main.cc @@ -16,6 +16,8 @@ #include #include +#include + #include "base/init.h" #include "base/proc_util.h" // for GetKernelVersion #include "facade/dragonfly_listener.h" @@ -28,6 +30,7 @@ #include "strings/human_readable.h" #include "util/accept_server.h" #include "util/epoll/epoll_pool.h" +#include "util/http/http_client.h" #include "util/uring/uring_pool.h" #include "util/varz.h" @@ -96,6 +99,122 @@ namespace dfly { namespace { +using util::http::TlsClient; + +const std::string_view kInvalidVersion{"unknown version"}; + +std::string GetVersionString(const std::string& from) { + // The server sends a message such as {"latest": "0.12.0"} + const auto reg_match_expr = R"(\{\"latest"\:[ \t]*\"([0-9]+\.[0-9]+\.[0-9]+)\"\})"; + VLOG(1) << "checking version '" << from << "'"; + auto const regex = std::regex(reg_match_expr); + std::smatch match; + if (std::regex_match(from, match, regex) && match.size() > 1) { + // the second entry is the match to the group that holds the version string + return match[1].str(); + } else { + return std::string{kInvalidVersion}; + } +} + +std::string GetRemoteVersion(ProactorBase* proactor, SSL_CTX* ssl_context, const std::string host, + std::string_view service, const std::string& resource) { + namespace bh = boost::beast::http; + using ResponseType = bh::response; + + TlsClient http_client{proactor}; + + http_client.set_connect_timeout_ms(2000); + auto version = proactor->Await([&] { + if (auto ec = http_client.Connect(host, service, ssl_context); !ec) { + bh::request req{bh::verb::get, resource, 11 /*http 1.1*/}; + req.set(bh::field::host, host.c_str()); + ResponseType res; + if (auto ec = http_client.Send(req, &res); !ec) { + VLOG(1) << "successfully got response from HTTP GET for host " << host << ":" << service + << "/" << resource << " response code is " << res.result(); + + if (res.result() == bh::status::ok) { + const auto& body = res.body(); + return GetVersionString(body); + } + } else { + LOG(WARNING) << "failed to process send HTTP GET to " << host << "@" << service + << " at resource '" << resource << "'"; + } + } else { + LOG(WARNING) << "failed to connect: " << ec.message(); + } + return std::string{kInvalidVersion}; + }); + + return version; +} + +struct VersionMonitor { + fibers_ext::Fiber version_fiber_; + fibers_ext::Done monitor_ver_done_; + ProactorPool* proactor_pool_ = nullptr; + + explicit VersionMonitor(ProactorPool* pp) : proactor_pool_{pp} { + } + + void Run() { + version_fiber_ = proactor_pool_->GetNextProactor()->LaunchFiber([this] { RunTask(); }); + } + + void Shutdown() { + monitor_ver_done_.Notify(); + if (version_fiber_.IsJoinable()) { + version_fiber_.Join(); + } + } + + private: + void RunTask(); +}; + +void VersionMonitor::RunTask() { + const auto loop_sleep_time = std::chrono::hours(24); // every 24 hours + + const std::string host_name = "run-stage-43h632k3xq-ew.a.run.app"; + const std::string_view port = "443"; + const std::string resource = "/v1"; + const std::string dev_version_name = "dev"; + + // Don't run in dev environment - i.e. where we don't build with + // real version number + if (kGitTag == dev_version_name) { + return; + } + + SSL_CTX* context = TlsClient::CreateSslContext(); + if (!context) { + LOG(WARNING) << "failed to create SSL context - cannot run version monitoring"; + return; + } + + auto CheckForNewerVersion = [&]() { + auto remote_version = + GetRemoteVersion(proactor_pool_->GetNextProactor(), context, host_name, port, resource); + if (remote_version != kGitTag) { + LOG_FIRST_N(INFO, 1) << "Your current version '" << kGitTag + << "' is not the latest version. A newer version '" << remote_version + << "' is now available. Please consider an update."; + } + }; + + CheckForNewerVersion(); + while (true) { + if (monitor_ver_done_.WaitFor(loop_sleep_time)) { + TlsClient::FreeContext(context); + VLOG(1) << "finish running version monitor task"; + return; + } + CheckForNewerVersion(); + } +} + enum class TermColor { kDefault, kRed, kGreen, kYellow }; // Returns the ANSI color code for the given color. TermColor::kDefault is // an invalid input. @@ -195,9 +314,12 @@ bool RunEngine(ProactorPool* pool, AcceptServer* acceptor) { acceptor->AddListener(mc_port, new Listener{Protocol::MEMCACHE, &service}); } + VersionMonitor version_monitor{pool}; + acceptor->Run(); + version_monitor.Run(); acceptor->Wait(); - + version_monitor.Shutdown(); service.Shutdown(); if (unlink_uds) {