|
23 | 23 | #endif
|
24 | 24 |
|
25 | 25 | #include <mimalloc.h>
|
26 |
| -#include <openssl/err.h> |
27 | 26 | #include <signal.h>
|
28 | 27 |
|
29 | 28 | #include <iostream>
|
30 | 29 | #include <memory>
|
31 |
| -#include <regex> |
32 | 30 |
|
33 | 31 | #include "base/init.h"
|
34 | 32 | #include "base/proc_util.h" // for GetKernelVersion
|
|
40 | 38 | #include "server/generic_family.h"
|
41 | 39 | #include "server/main_service.h"
|
42 | 40 | #include "server/version.h"
|
| 41 | +#include "server/version_monitor.h" |
43 | 42 | #include "strings/human_readable.h"
|
44 | 43 | #include "util/accept_server.h"
|
45 | 44 | #include "util/fibers/pool.h"
|
46 |
| -#include "util/http/http_client.h" |
47 | 45 | #include "util/varz.h"
|
48 | 46 |
|
49 | 47 | #ifdef __APPLE__
|
@@ -88,189 +86,6 @@ namespace {
|
88 | 86 |
|
89 | 87 | using util::http::TlsClient;
|
90 | 88 |
|
91 |
| -std::optional<std::string> GetVersionString(const std::string& version_str) { |
92 |
| - // The server sends a message such as {"latest": "0.12.0"} |
93 |
| - const auto reg_match_expr = R"(\{\"latest"\:[ \t]*\"([0-9]+\.[0-9]+\.[0-9]+)\"\})"; |
94 |
| - VLOG(1) << "checking version '" << version_str << "'"; |
95 |
| - auto const regex = std::regex(reg_match_expr); |
96 |
| - std::smatch match; |
97 |
| - if (std::regex_match(version_str, match, regex) && match.size() > 1) { |
98 |
| - // the second entry is the match to the group that holds the version string |
99 |
| - return match[1].str(); |
100 |
| - } else { |
101 |
| - LOG_FIRST_N(WARNING, 1) << "Remote version - invalid version number: '" << version_str << "'"; |
102 |
| - return std::nullopt; |
103 |
| - } |
104 |
| -} |
105 |
| - |
106 |
| -std::optional<std::string> GetRemoteVersion(ProactorBase* proactor, SSL_CTX* ssl_context, |
107 |
| - const std::string host, std::string_view service, |
108 |
| - const std::string& resource, |
109 |
| - const std::string& ver_header) { |
110 |
| - namespace bh = boost::beast::http; |
111 |
| - using ResponseType = bh::response<bh::string_body>; |
112 |
| - |
113 |
| - bh::request<bh::string_body> req{bh::verb::get, resource, 11 /*http 1.1*/}; |
114 |
| - req.set(bh::field::host, host); |
115 |
| - req.set(bh::field::user_agent, ver_header); |
116 |
| - ResponseType res; |
117 |
| - TlsClient http_client{proactor}; |
118 |
| - http_client.set_connect_timeout_ms(2000); |
119 |
| - |
120 |
| - auto ec = http_client.Connect(host, service, ssl_context); |
121 |
| - |
122 |
| - if (ec) { |
123 |
| - LOG_FIRST_N(WARNING, 1) << "Remote version - connection error [" << host << ":" << service |
124 |
| - << "] : " << ec.message(); |
125 |
| - return nullopt; |
126 |
| - } |
127 |
| - |
128 |
| - ec = http_client.Send(req, &res); |
129 |
| - if (!ec) { |
130 |
| - VLOG(1) << "successfully got response from HTTP GET for host " << host << ":" << service << "/" |
131 |
| - << resource << " response code is " << res.result(); |
132 |
| - |
133 |
| - if (res.result() == bh::status::ok) { |
134 |
| - return GetVersionString(res.body()); |
135 |
| - } |
136 |
| - } else { |
137 |
| - static bool is_logged{false}; |
138 |
| - if (!is_logged) { |
139 |
| - is_logged = true; |
140 |
| - |
141 |
| -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) |
142 |
| - const char* func_err = "ssl_internal_error"; |
143 |
| -#else |
144 |
| - const char* func_err = ERR_func_error_string(ec.value()); |
145 |
| -#endif |
146 |
| - |
147 |
| - // Unfortunately AsioStreamAdapter looses the original error category |
148 |
| - // because std::error_code can not be converted into boost::system::error_code. |
149 |
| - // It's fixed in later versions of Boost, but for now we assume it's from TLS. |
150 |
| - LOG(WARNING) << "Remote version - HTTP GET error [" << host << ":" << service << resource |
151 |
| - << "], error: " << ec.value(); |
152 |
| - LOG(WARNING) << "ssl error: " << func_err << "/" << ERR_reason_error_string(ec.value()); |
153 |
| - } |
154 |
| - } |
155 |
| - |
156 |
| - return nullopt; |
157 |
| -} |
158 |
| - |
159 |
| -struct VersionMonitor { |
160 |
| - Fiber version_fiber_; |
161 |
| - Done monitor_ver_done_; |
162 |
| - |
163 |
| - void Run(ProactorPool* proactor_pool); |
164 |
| - |
165 |
| - void Shutdown() { |
166 |
| - monitor_ver_done_.Notify(); |
167 |
| - if (version_fiber_.IsJoinable()) { |
168 |
| - version_fiber_.Join(); |
169 |
| - } |
170 |
| - } |
171 |
| - |
172 |
| - private: |
173 |
| - struct SslDeleter { |
174 |
| - void operator()(SSL_CTX* ssl) { |
175 |
| - if (ssl) { |
176 |
| - TlsClient::FreeContext(ssl); |
177 |
| - } |
178 |
| - } |
179 |
| - }; |
180 |
| - |
181 |
| - using SslPtr = std::unique_ptr<SSL_CTX, SslDeleter>; |
182 |
| - void RunTask(SslPtr); |
183 |
| - |
184 |
| - bool IsVersionOutdated(std::string_view remote, std::string_view current) const; |
185 |
| -}; |
186 |
| - |
187 |
| -bool VersionMonitor::IsVersionOutdated(const std::string_view remote, |
188 |
| - const std::string_view current) const { |
189 |
| - const absl::InlinedVector<absl::string_view, 3> remote_xyz = absl::StrSplit(remote, "."); |
190 |
| - const absl::InlinedVector<absl::string_view, 3> current_xyz = absl::StrSplit(current, "."); |
191 |
| - if (remote_xyz.size() != current_xyz.size()) { |
192 |
| - LOG(WARNING) << "Can't compare Dragonfly version " << current << " to latest version " |
193 |
| - << remote; |
194 |
| - return false; |
195 |
| - } |
196 |
| - const auto print_to_log = [](const std::string_view version, const absl::string_view part) { |
197 |
| - LOG(WARNING) << "Can't parse " << version << " part of version " << part << " as a number"; |
198 |
| - }; |
199 |
| - for (size_t i = 0; i < remote_xyz.size(); ++i) { |
200 |
| - size_t remote_x = 0; |
201 |
| - if (!absl::SimpleAtoi(remote_xyz[i], &remote_x)) { |
202 |
| - print_to_log(remote, remote_xyz[i]); |
203 |
| - return false; |
204 |
| - } |
205 |
| - size_t current_x = 0; |
206 |
| - if (!absl::SimpleAtoi(current_xyz[i], ¤t_x)) { |
207 |
| - print_to_log(current, current_xyz[i]); |
208 |
| - return false; |
209 |
| - } |
210 |
| - if (remote_x > current_x) { |
211 |
| - return true; |
212 |
| - } |
213 |
| - |
214 |
| - if (remote_x < current_x) { |
215 |
| - return false; |
216 |
| - } |
217 |
| - } |
218 |
| - |
219 |
| - return false; |
220 |
| -} |
221 |
| - |
222 |
| -void VersionMonitor::Run(ProactorPool* proactor_pool) { |
223 |
| - // Avoid running dev environments. |
224 |
| - if (getenv("DFLY_DEV_ENV")) { |
225 |
| - LOG(WARNING) << "Running in dev environment (DFLY_DEV_ENV is set) - version monitoring is " |
226 |
| - "disabled"; |
227 |
| - return; |
228 |
| - } |
229 |
| - // not a production release tag. |
230 |
| - if (!GetFlag(FLAGS_version_check) || kGitTag[0] != 'v' || strchr(kGitTag, '-')) { |
231 |
| - return; |
232 |
| - } |
233 |
| - |
234 |
| - SslPtr ssl_ctx(TlsClient::CreateSslContext()); |
235 |
| - if (!ssl_ctx) { |
236 |
| - VLOG(1) << "Remote version - failed to create SSL context - cannot run version monitoring"; |
237 |
| - return; |
238 |
| - } |
239 |
| - |
240 |
| - version_fiber_ = proactor_pool->GetNextProactor()->LaunchFiber( |
241 |
| - [ssl_ctx = std::move(ssl_ctx), this]() mutable { RunTask(std::move(ssl_ctx)); }); |
242 |
| -} |
243 |
| - |
244 |
| -void VersionMonitor::RunTask(SslPtr ssl_ctx) { |
245 |
| - const auto loop_sleep_time = std::chrono::hours(24); // every 24 hours |
246 |
| - |
247 |
| - const std::string host_name = "version.dragonflydb.io"; |
248 |
| - const std::string_view port = "443"; |
249 |
| - const std::string resource = "/v1"; |
250 |
| - string_view current_version(kGitTag); |
251 |
| - |
252 |
| - current_version.remove_prefix(1); |
253 |
| - const std::string version_header = absl::StrCat("DragonflyDB/", current_version); |
254 |
| - |
255 |
| - ProactorBase* my_pb = ProactorBase::me(); |
256 |
| - while (true) { |
257 |
| - const std::optional<std::string> remote_version = |
258 |
| - GetRemoteVersion(my_pb, ssl_ctx.get(), host_name, port, resource, version_header); |
259 |
| - if (remote_version) { |
260 |
| - const std::string_view rv = remote_version.value(); |
261 |
| - if (IsVersionOutdated(rv, current_version)) { |
262 |
| - LOG_FIRST_N(INFO, 1) << "Your current version '" << current_version |
263 |
| - << "' is not the latest version. A newer version '" << rv |
264 |
| - << "' is now available. Please consider an update."; |
265 |
| - } |
266 |
| - } |
267 |
| - if (monitor_ver_done_.WaitFor(loop_sleep_time)) { |
268 |
| - VLOG(1) << "finish running version monitor task"; |
269 |
| - return; |
270 |
| - } |
271 |
| - } |
272 |
| -} |
273 |
| - |
274 | 89 | enum class TermColor { kDefault, kRed, kGreen, kYellow };
|
275 | 90 | // Returns the ANSI color code for the given color. TermColor::kDefault is
|
276 | 91 | // an invalid input.
|
@@ -459,7 +274,11 @@ bool RunEngine(ProactorPool* pool, AcceptServer* acceptor) {
|
459 | 274 | service.Init(acceptor, listeners, opts);
|
460 | 275 |
|
461 | 276 | VersionMonitor version_monitor;
|
462 |
| - version_monitor.Run(pool); |
| 277 | + |
| 278 | + // check if it's a production release tag. |
| 279 | + if (GetFlag(FLAGS_version_check) && kGitTag[0] == 'v' && strchr(kGitTag, '-') == nullptr) { |
| 280 | + version_monitor.Run(pool); |
| 281 | + } |
463 | 282 |
|
464 | 283 | // Start the acceptor loop and wait for the server to shutdown.
|
465 | 284 | acceptor->Run();
|
@@ -733,9 +552,10 @@ void PrintBasicUsageInfo() {
|
733 | 552 |
|
734 | 553 | void ParseFlagsFromEnv() {
|
735 | 554 | if (getenv("DFLY_PASSWORD")) {
|
736 |
| - LOG(WARNING) |
| 555 | + LOG(FATAL) |
737 | 556 | << "DFLY_PASSWORD environment variable is being deprecated in favour of DFLY_requirepass";
|
738 | 557 | }
|
| 558 | + |
739 | 559 | // Allowed environment variable names that can have
|
740 | 560 | // DFLY_ prefix, but don't necessarily have an ABSL flag created
|
741 | 561 | absl::flat_hash_set<std::string_view> ignored_environment_flag_names = {"DEV_ENV", "PASSWORD"};
|
|
0 commit comments