/Users/deen/code/yugabyte-db/src/yb/yql/pgwrapper/pg_wrapper.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | |
13 | | #include "yb/yql/pgwrapper/pg_wrapper.h" |
14 | | |
15 | | #include <signal.h> |
16 | | |
17 | | #include <fstream> |
18 | | #include <random> |
19 | | #include <regex> |
20 | | #include <string> |
21 | | #include <thread> |
22 | | #include <vector> |
23 | | |
24 | | #include <boost/algorithm/string.hpp> |
25 | | |
26 | | #include "yb/util/env_util.h" |
27 | | #include "yb/util/errno.h" |
28 | | #include "yb/util/flag_tags.h" |
29 | | #include "yb/util/logging.h" |
30 | | #include "yb/util/net/net_util.h" |
31 | | #include "yb/util/path_util.h" |
32 | | #include "yb/util/pg_util.h" |
33 | | #include "yb/util/result.h" |
34 | | #include "yb/util/scope_exit.h" |
35 | | #include "yb/util/status_format.h" |
36 | | #include "yb/util/status_log.h" |
37 | | #include "yb/util/subprocess.h" |
38 | | #include "yb/util/thread.h" |
39 | | |
40 | | DEFINE_string(pg_proxy_bind_address, "", "Address for the PostgreSQL proxy to bind to"); |
41 | | DEFINE_string(postmaster_cgroup, "", "cgroup to add postmaster process to"); |
42 | | DEFINE_bool(pg_transactions_enabled, true, |
43 | | "True to enable transactions in YugaByte PostgreSQL API."); |
44 | | DEFINE_bool(pg_verbose_error_log, false, |
45 | | "True to enable verbose logging of errors in PostgreSQL server"); |
46 | | DEFINE_int32(pgsql_proxy_webserver_port, 13000, "Webserver port for PGSQL"); |
47 | | |
48 | | DEFINE_test_flag(bool, pg_collation_enabled, true, |
49 | | "True to enable collation support in YugaByte PostgreSQL."); |
50 | | |
51 | | DECLARE_string(metric_node_name); |
52 | | TAG_FLAG(pg_transactions_enabled, advanced); |
53 | | TAG_FLAG(pg_transactions_enabled, hidden); |
54 | | |
55 | | DEFINE_bool(pg_stat_statements_enabled, true, |
56 | | "True to enable statement stats in PostgreSQL server"); |
57 | | TAG_FLAG(pg_stat_statements_enabled, advanced); |
58 | | TAG_FLAG(pg_stat_statements_enabled, hidden); |
59 | | |
60 | | // Top-level postgres configuration flags. |
61 | | DEFINE_bool(ysql_enable_auth, false, |
62 | | "True to enforce password authentication for all connections"); |
63 | | DEFINE_string(ysql_timezone, "", |
64 | | "Overrides the default ysql timezone for displaying and interpreting timestamps"); |
65 | | DEFINE_string(ysql_datestyle, "", |
66 | | "Overrides the default ysql display format for date and time values"); |
67 | | DEFINE_int32(ysql_max_connections, 0, |
68 | | "Overrides the maximum number of concurrent ysql connections"); |
69 | | DEFINE_string(ysql_default_transaction_isolation, "", |
70 | | "Overrides the default ysql transaction isolation level"); |
71 | | DEFINE_string(ysql_log_statement, "", |
72 | | "Sets which types of ysql statements should be logged"); |
73 | | DEFINE_string(ysql_log_min_messages, "", |
74 | | "Sets the lowest ysql message level to log"); |
75 | | DEFINE_string(ysql_log_min_duration_statement, "", |
76 | | "Sets the duration of each completed ysql statement to be logged if the statement" \ |
77 | | " ran for at least the specified number of milliseconds."); |
78 | | |
79 | | |
80 | | // Catch-all postgres configuration flags. |
81 | | DEFINE_string(ysql_pg_conf_csv, "", |
82 | | "CSV formatted line represented list of postgres setting assignments"); |
83 | | DEFINE_string(ysql_hba_conf_csv, "", |
84 | | "CSV formatted line represented list of postgres hba rules (in order)"); |
85 | | TAG_FLAG(ysql_hba_conf_csv, sensitive_info); |
86 | | |
87 | | DEFINE_string(ysql_pg_conf, "", |
88 | | "Deprecated, use the `ysql_pg_conf_csv` flag instead. " \ |
89 | | "Comma separated list of postgres setting assignments"); |
90 | | DEFINE_string(ysql_hba_conf, "", |
91 | | "Deprecated, use `ysql_hba_conf_csv` flag instead. " \ |
92 | | "Comma separated list of postgres hba rules (in order)"); |
93 | | TAG_FLAG(ysql_hba_conf, sensitive_info); |
94 | | |
95 | | using std::vector; |
96 | | using std::string; |
97 | | |
98 | | using namespace std::literals; // NOLINT |
99 | | |
100 | | namespace yb { |
101 | | namespace pgwrapper { |
102 | | |
103 | | namespace { |
104 | | |
105 | 3.99k | Status WriteConfigFile(const string& path, const vector<string>& lines) { |
106 | 3.99k | std::ofstream conf_file; |
107 | 3.99k | conf_file.open(path, std::ios_base::out | std::ios_base::trunc); |
108 | 3.99k | if (!conf_file) { |
109 | 0 | return STATUS_FORMAT( |
110 | 0 | IOError, |
111 | 0 | "Failed to write ysql config file '%s': errno=$0: $1", |
112 | 0 | path, |
113 | 0 | errno, |
114 | 0 | ErrnoToString(errno)); |
115 | 0 | } |
116 | | |
117 | 3.99k | conf_file << "# This is an autogenerated file, do not edit manually!" << std::endl; |
118 | 1.41M | for (const auto& line : lines) { |
119 | 1.41M | conf_file << line << std::endl; |
120 | 1.41M | } |
121 | | |
122 | 3.99k | conf_file.close(); |
123 | | |
124 | 3.99k | return Status::OK(); |
125 | 3.99k | } |
126 | | |
127 | 324 | void ReadCommaSeparatedValues(const string& src, vector<string>* lines) { |
128 | 324 | vector<string> new_lines; |
129 | 324 | boost::split(new_lines, src, boost::is_any_of(",")); |
130 | 324 | lines->insert(lines->end(), new_lines.begin(), new_lines.end()); |
131 | 324 | } |
132 | | |
133 | 3 | void MergeSharedPreloadLibraries(const string& src, vector<string>* defaults) { |
134 | 3 | string copy = boost::replace_all_copy(src, " ", ""); |
135 | 3 | copy = boost::erase_first_copy(copy, "shared_preload_libraries"); |
136 | | // According to the documentation in postgresql.conf file, |
137 | | // the '=' is optional hence it needs to be handled separately. |
138 | 3 | copy = boost::erase_first_copy(copy, "="); |
139 | 3 | copy = boost::trim_copy_if(copy, boost::is_any_of("'\"")); |
140 | 3 | vector<string> new_items; |
141 | 3 | boost::split(new_items, copy, boost::is_any_of(",")); |
142 | | // Remove empty elements, makes it safe to use with empty user |
143 | | // provided shared_preload_libraries, for example, |
144 | | // if the value was provided via environment variable, example: |
145 | | // |
146 | | // --ysql_pg_conf="shared_preload_libraries='$UNSET_VALUE'" |
147 | | // |
148 | | // Alternative example: |
149 | | // |
150 | | // --ysql_pg_conf="shared_preload_libraries='$LIB1,$LIB2,$LIB3'" |
151 | | // where any of the libs could be undefined. |
152 | 3 | new_items.erase( |
153 | 3 | std::remove_if(new_items.begin(), |
154 | 3 | new_items.end(), |
155 | 3 | [](const std::string& s){return s.empty();}), |
156 | 3 | new_items.end()); |
157 | 3 | defaults->insert(defaults->end(), new_items.begin(), new_items.end()); |
158 | 3 | } |
159 | | |
160 | 41 | CHECKED_STATUS ReadCSVValues(const string& csv, vector<string>* lines) { |
161 | | // Function reads CSV string in the following format: |
162 | | // - fields are divided with comma (,) |
163 | | // - fields with comma (,) or double-quote (") are quoted with double-quote (") |
164 | | // - pair of double-quote ("") in quoted field represents single double-quote (") |
165 | | // |
166 | | // Examples: |
167 | | // 1,"two, 2","three ""3""", four , -> ['1', 'two, 2', 'three "3"', ' four ', ''] |
168 | | // 1,"two -> Malformed CSV (quoted field 'two' is not closed) |
169 | | // 1, "two" -> Malformed CSV (quoted field 'two' has leading spaces) |
170 | | // 1,two "2" -> Malformed CSV (field with " must be quoted) |
171 | | // 1,"tw"o" -> Malformed CSV (no separator after quoted field 'tw') |
172 | | |
173 | 41 | const std::regex exp(R"(^(?:([^,"]+)|(?:"((?:[^"]|(?:""))*)\"))(?:(?:,)|(?:$)))"); |
174 | 41 | auto i = csv.begin(); |
175 | 41 | const auto end = csv.end(); |
176 | 41 | std::smatch match; |
177 | 84 | while (i != end && std::regex_search(i, end, match, exp)43 ) { |
178 | | // Replace pair of double-quote ("") with single double-quote (") in quoted field. |
179 | 43 | if (match[2].length() > 0) { |
180 | 0 | lines->emplace_back(match[2].first, match[2].second); |
181 | 0 | boost::algorithm::replace_all(lines->back(), "\"\"", "\""); |
182 | 43 | } else { |
183 | 43 | lines->emplace_back(match[1].first, match[1].second); |
184 | 43 | } |
185 | 43 | i += match.length(); |
186 | 43 | } |
187 | 41 | SCHECK(i == end, InvalidArgument, Format("Malformed CSV '$0'", csv)); |
188 | 41 | if (!csv.empty() && csv.back() == ',') { |
189 | 0 | lines->emplace_back(); |
190 | 0 | } |
191 | 41 | return Status::OK(); |
192 | 41 | } |
193 | | |
194 | 2.00k | Result<string> WritePostgresConfig(const PgProcessConf& conf) { |
195 | | // First add default configuration created by local initdb. |
196 | 2.00k | string default_conf_path = JoinPathSegments(conf.data_dir, "postgresql.conf"); |
197 | 2.00k | std::ifstream conf_file; |
198 | 2.00k | conf_file.open(default_conf_path, std::ios_base::in); |
199 | 2.00k | if (!conf_file) { |
200 | 3 | return STATUS_FORMAT( |
201 | 3 | IOError, |
202 | 3 | "Failed to read default postgres configuration '%s': errno=$0: $1", |
203 | 3 | default_conf_path, |
204 | 3 | errno, |
205 | 3 | ErrnoToString(errno)); |
206 | 3 | } |
207 | | |
208 | | // Gather the default extensions: |
209 | 1.99k | vector<string> metricsLibs; |
210 | 1.99k | if (FLAGS_pg_stat_statements_enabled) { |
211 | 1.99k | metricsLibs.push_back("pg_stat_statements"); |
212 | 1.99k | } |
213 | 1.99k | metricsLibs.push_back("yb_pg_metrics"); |
214 | 1.99k | metricsLibs.push_back("pgaudit"); |
215 | 1.99k | metricsLibs.push_back("pg_hint_plan"); |
216 | | |
217 | 1.99k | vector<string> lines; |
218 | 1.99k | string line; |
219 | 1.41M | while (std::getline(conf_file, line)) { |
220 | 1.40M | lines.push_back(line); |
221 | 1.40M | } |
222 | 1.99k | conf_file.close(); |
223 | | |
224 | 1.99k | vector<string> user_configs; |
225 | 1.99k | if (!FLAGS_ysql_pg_conf_csv.empty()) { |
226 | 34 | RETURN_NOT_OK(ReadCSVValues(FLAGS_ysql_pg_conf_csv, &user_configs)); |
227 | 1.96k | } else if (!FLAGS_ysql_pg_conf.empty()) { |
228 | 30 | ReadCommaSeparatedValues(FLAGS_ysql_pg_conf, &user_configs); |
229 | 30 | } |
230 | | |
231 | | // If the user has given any shared_preload_libraries, merge them in. |
232 | 1.99k | for (string &value : user_configs) { |
233 | 72 | if (boost::starts_with(value, "shared_preload_libraries")) { |
234 | 3 | MergeSharedPreloadLibraries(value, &metricsLibs); |
235 | 69 | } else { |
236 | 69 | lines.push_back(value); |
237 | 69 | } |
238 | 72 | } |
239 | | |
240 | | // Add shared_preload_libraries to the ysql_pg.conf. |
241 | 1.99k | lines.push_back(Format("shared_preload_libraries='$0'", boost::join(metricsLibs, ","))); |
242 | | |
243 | 1.99k | if (conf.enable_tls) { |
244 | 11 | lines.push_back("ssl=on"); |
245 | 11 | lines.push_back(Format("ssl_cert_file='$0/node.$1.crt'", |
246 | 11 | conf.certs_for_client_dir, |
247 | 11 | conf.cert_base_name)); |
248 | 11 | lines.push_back(Format("ssl_key_file='$0/node.$1.key'", |
249 | 11 | conf.certs_for_client_dir, |
250 | 11 | conf.cert_base_name)); |
251 | 11 | lines.push_back(Format("ssl_ca_file='$0/ca.crt'", conf.certs_for_client_dir)); |
252 | 11 | } |
253 | | |
254 | 1.99k | if (!FLAGS_ysql_timezone.empty()) { |
255 | 2 | lines.push_back("timezone=" + FLAGS_ysql_timezone); |
256 | 2 | } |
257 | | |
258 | 1.99k | if (!FLAGS_ysql_datestyle.empty()) { |
259 | 6 | lines.push_back("datestyle=" + FLAGS_ysql_datestyle); |
260 | 6 | } |
261 | | |
262 | 1.99k | if (FLAGS_ysql_max_connections > 0) { |
263 | 8 | lines.push_back("max_connections=" + std::to_string(FLAGS_ysql_max_connections)); |
264 | 8 | } |
265 | | |
266 | 1.99k | if (!FLAGS_ysql_default_transaction_isolation.empty()) { |
267 | 4 | lines.push_back("default_transaction_isolation=" + FLAGS_ysql_default_transaction_isolation); |
268 | 4 | } |
269 | | |
270 | 1.99k | if (!FLAGS_ysql_log_statement.empty()) { |
271 | 4 | lines.push_back("log_statement=" + FLAGS_ysql_log_statement); |
272 | 4 | } |
273 | | |
274 | 1.99k | if (!FLAGS_ysql_log_min_messages.empty()) { |
275 | 4 | lines.push_back("log_min_messages=" + FLAGS_ysql_log_min_messages); |
276 | 4 | } |
277 | | |
278 | 1.99k | if (!FLAGS_ysql_log_min_duration_statement.empty()) { |
279 | 4 | lines.push_back("log_min_duration_statement=" + FLAGS_ysql_log_min_duration_statement); |
280 | 4 | } |
281 | | |
282 | 1.99k | string conf_path = JoinPathSegments(conf.data_dir, "ysql_pg.conf"); |
283 | 1.99k | RETURN_NOT_OK(WriteConfigFile(conf_path, lines)); |
284 | 1.99k | return "config_file=" + conf_path; |
285 | 1.99k | } |
286 | | |
287 | 1.99k | Result<string> WritePgHbaConfig(const PgProcessConf& conf) { |
288 | 1.99k | vector<string> lines; |
289 | | |
290 | | // Add the user-defined custom configuration lines if any. |
291 | | // Put this first so that it can be used to override the auto-generated config below. |
292 | 1.99k | if (!FLAGS_ysql_hba_conf_csv.empty()) { |
293 | 7 | RETURN_NOT_OK(ReadCSVValues(FLAGS_ysql_hba_conf_csv, &lines)); |
294 | 1.99k | } else if (!FLAGS_ysql_hba_conf.empty()) { |
295 | 294 | ReadCommaSeparatedValues(FLAGS_ysql_hba_conf, &lines); |
296 | 294 | } |
297 | | |
298 | | // Add auto-generated config for the enable auth and enable_tls flags. |
299 | 1.99k | if (FLAGS_ysql_enable_auth || conf.enable_tls1.98k ) { |
300 | 18 | const auto host_type = conf.enable_tls ? "hostssl"11 : "host"7 ; |
301 | 18 | const auto auth_method = FLAGS_ysql_enable_auth ? "md5"11 : "trust"7 ; |
302 | 18 | lines.push_back(Format("$0 all all all $1", host_type, auth_method)); |
303 | 18 | } |
304 | | |
305 | | // Enforce a default hba configuration so users don't lock themselves out. |
306 | 1.99k | if (lines.empty()) { |
307 | 1.68k | LOG(WARNING) << "No hba configuration lines found, defaulting to trust all configuration."; |
308 | 1.68k | lines.push_back("host all all all trust"); |
309 | 1.68k | } |
310 | | |
311 | | // Add comments to the hba config file noting the internally hardcoded config line. |
312 | 1.99k | lines.insert(lines.begin(), { |
313 | 1.99k | "# Internal configuration:", |
314 | 1.99k | "# local all postgres yb-tserver-key", |
315 | 1.99k | }); |
316 | | |
317 | 1.99k | const auto conf_path = JoinPathSegments(conf.data_dir, "ysql_hba.conf"); |
318 | 1.99k | RETURN_NOT_OK(WriteConfigFile(conf_path, lines)); |
319 | 1.99k | return "hba_file=" + conf_path; |
320 | 1.99k | } |
321 | | |
322 | 2.00k | Result<vector<string>> WritePgConfigFiles(const PgProcessConf& conf) { |
323 | 2.00k | vector<string> args; |
324 | 2.00k | args.push_back("-c"); |
325 | 2.00k | args.push_back(VERIFY_RESULT_PREPEND1.99k (WritePostgresConfig(conf), |
326 | 1.99k | "Failed to write ysql pg configuration: ")); |
327 | 0 | args.push_back("-c"); |
328 | 1.99k | args.push_back(VERIFY_RESULT_PREPEND(WritePgHbaConfig(conf), |
329 | 1.99k | "Failed to write ysql hba configuration: ")); |
330 | 0 | return args; |
331 | 1.99k | } |
332 | | |
333 | | } // namespace |
334 | | |
335 | 12.0k | string GetPostgresInstallRoot() { |
336 | 12.0k | return JoinPathSegments(yb::env_util::GetRootDir("postgres"), "postgres"); |
337 | 12.0k | } |
338 | | |
339 | | Result<PgProcessConf> PgProcessConf::CreateValidateAndRunInitDb( |
340 | | const std::string& bind_addresses, |
341 | | const std::string& data_dir, |
342 | 2.01k | const int tserver_shm_fd) { |
343 | 2.01k | PgProcessConf conf; |
344 | 2.01k | if (!bind_addresses.empty()) { |
345 | 2.01k | auto pg_host_port = VERIFY_RESULT(HostPort::FromString( |
346 | 2.01k | bind_addresses, PgProcessConf::kDefaultPort)); |
347 | 0 | conf.listen_addresses = pg_host_port.host(); |
348 | 2.01k | conf.pg_port = pg_host_port.port(); |
349 | 2.01k | } |
350 | 2.01k | conf.data_dir = data_dir; |
351 | 2.01k | conf.tserver_shm_fd = tserver_shm_fd; |
352 | 2.01k | PgWrapper pg_wrapper(conf); |
353 | 2.01k | RETURN_NOT_OK(pg_wrapper.PreflightCheck()); |
354 | 2.01k | RETURN_NOT_OK(pg_wrapper.InitDbLocalOnlyIfNeeded()); |
355 | 2.01k | return conf; |
356 | 2.01k | } |
357 | | |
358 | | // ------------------------------------------------------------------------------------------------ |
359 | | // PgWrapper: managing one instance of a PostgreSQL child process |
360 | | // ------------------------------------------------------------------------------------------------ |
361 | | |
362 | | PgWrapper::PgWrapper(PgProcessConf conf) |
363 | 4.01k | : conf_(std::move(conf)) { |
364 | 4.01k | } |
365 | | |
366 | 2.01k | Status PgWrapper::PreflightCheck() { |
367 | 2.01k | RETURN_NOT_OK(CheckExecutableValid(GetPostgresExecutablePath())); |
368 | 2.01k | RETURN_NOT_OK(CheckExecutableValid(GetInitDbExecutablePath())); |
369 | 2.01k | return Status::OK(); |
370 | 2.01k | } |
371 | | |
372 | 2.00k | Status PgWrapper::Start() { |
373 | 2.00k | auto postgres_executable = GetPostgresExecutablePath(); |
374 | 2.00k | RETURN_NOT_OK(CheckExecutableValid(postgres_executable)); |
375 | 2.00k | vector<string> argv { |
376 | 2.00k | postgres_executable, |
377 | 2.00k | "-D", conf_.data_dir, |
378 | 2.00k | "-p", std::to_string(conf_.pg_port), |
379 | 2.00k | "-h", conf_.listen_addresses, |
380 | 2.00k | }; |
381 | | |
382 | 2.00k | bool log_to_file = !FLAGS_logtostderr && !FLAGS_log_dir.empty()2 && !conf_.force_disable_log_file2 ; |
383 | 2.00k | VLOG(1) << "Deciding whether the child postgres process should to file: " |
384 | 0 | << EXPR_VALUE_FOR_LOG(FLAGS_logtostderr) << ", " |
385 | 0 | << EXPR_VALUE_FOR_LOG(FLAGS_log_dir.empty()) << ", " |
386 | 0 | << EXPR_VALUE_FOR_LOG(conf_.force_disable_log_file) << ": " |
387 | 0 | << EXPR_VALUE_FOR_LOG(log_to_file); |
388 | | |
389 | | // Configure UNIX domain socket for index backfill tserver-postgres communication and for |
390 | | // Yugabyte Platform backups. |
391 | 2.00k | argv.push_back("-k"); |
392 | 2.00k | const std::string& socket_dir = PgDeriveSocketDir(conf_.listen_addresses); |
393 | 2.00k | RETURN_NOT_OK(Env::Default()->CreateDirs(socket_dir)); |
394 | 2.00k | argv.push_back(socket_dir); |
395 | | |
396 | | // Also tighten permissions on the socket. |
397 | 2.00k | argv.push_back("-c"); |
398 | 2.00k | argv.push_back("unix_socket_permissions=0700"); |
399 | | |
400 | 2.00k | if (log_to_file) { |
401 | 1 | argv.push_back("-c"); |
402 | 1 | argv.push_back("logging_collector=on"); |
403 | | // FLAGS_log_dir should already be set by tserver during startup. |
404 | 1 | argv.push_back("-c"); |
405 | 1 | argv.push_back("log_directory=" + FLAGS_log_dir); |
406 | 1 | } |
407 | | |
408 | 2.00k | argv.push_back("-c"); |
409 | 2.00k | argv.push_back("yb_pg_metrics.node_name=" + FLAGS_metric_node_name); |
410 | 2.00k | argv.push_back("-c"); |
411 | 2.00k | argv.push_back("yb_pg_metrics.port=" + std::to_string(FLAGS_pgsql_proxy_webserver_port)); |
412 | | |
413 | 2.00k | auto config_file_args = CHECK_RESULT(WritePgConfigFiles(conf_)); |
414 | 2.00k | argv.insert(argv.end(), config_file_args.begin(), config_file_args.end()); |
415 | | |
416 | 2.00k | if (FLAGS_pg_verbose_error_log) { |
417 | 0 | argv.push_back("-c"); |
418 | 0 | argv.push_back("log_error_verbosity=VERBOSE"); |
419 | 0 | } |
420 | | |
421 | 2.00k | pg_proc_.emplace(postgres_executable, argv); |
422 | 2.00k | vector<string> ld_library_path { |
423 | 2.00k | GetPostgresLibPath(), |
424 | 2.00k | GetPostgresThirdPartyLibPath() |
425 | 2.00k | }; |
426 | 2.00k | pg_proc_->SetEnv("LD_LIBRARY_PATH", boost::join(ld_library_path, ":")); |
427 | 2.00k | pg_proc_->ShareParentStderr(); |
428 | 2.00k | pg_proc_->ShareParentStdout(); |
429 | | // See YBSetParentDeathSignal in pg_yb_utils.c for how this is used. |
430 | 2.00k | pg_proc_->SetEnv("YB_PG_PDEATHSIG", Format("$0", SIGINT)); |
431 | 2.00k | pg_proc_->InheritNonstandardFd(conf_.tserver_shm_fd); |
432 | 2.00k | const char* llvm_profile_env_var_value = getenv("LLVM_PROFILE_FILE"); |
433 | 2.00k | pg_proc_->SetEnv("LLVM_PROFILE_FILE", Format("$0_postgres.%p", llvm_profile_env_var_value)); |
434 | 2.00k | SetCommonEnv(&pg_proc_.get(), /* yb_enabled */ true); |
435 | 2.00k | RETURN_NOT_OK(pg_proc_->Start()); |
436 | 2.00k | if (!FLAGS_postmaster_cgroup.empty()) { |
437 | 0 | std::string path = FLAGS_postmaster_cgroup + "/cgroup.procs"; |
438 | 0 | pg_proc_->AddPIDToCGroup(path, pg_proc_->pid()); |
439 | 0 | } |
440 | 2.00k | LOG(INFO) << "PostgreSQL server running as pid " << pg_proc_->pid(); |
441 | 2.00k | return Status::OK(); |
442 | 2.00k | } |
443 | | |
444 | 0 | void PgWrapper::Kill() { |
445 | 0 | WARN_NOT_OK(pg_proc_->Kill(SIGQUIT), "Kill PostgreSQL server failed"); |
446 | 0 | } |
447 | | |
448 | 2.00k | Status PgWrapper::InitDb(bool yb_enabled) { |
449 | 2.00k | const string initdb_program_path = GetInitDbExecutablePath(); |
450 | 2.00k | RETURN_NOT_OK(CheckExecutableValid(initdb_program_path)); |
451 | 2.00k | if (!Env::Default()->FileExists(initdb_program_path)) { |
452 | 0 | return STATUS_FORMAT(IOError, "initdb not found at: $0", initdb_program_path); |
453 | 0 | } |
454 | | |
455 | 2.00k | vector<string> initdb_args { initdb_program_path, "-D", conf_.data_dir, "-U", "postgres" }; |
456 | 2.00k | LOG(INFO) << "Launching initdb: " << AsString(initdb_args); |
457 | | |
458 | 2.00k | Subprocess initdb_subprocess(initdb_program_path, initdb_args); |
459 | 2.00k | initdb_subprocess.InheritNonstandardFd(conf_.tserver_shm_fd); |
460 | 2.00k | SetCommonEnv(&initdb_subprocess, yb_enabled); |
461 | 2.00k | int status = 0; |
462 | 2.00k | RETURN_NOT_OK(initdb_subprocess.Start()); |
463 | 2.00k | RETURN_NOT_OK(initdb_subprocess.Wait(&status)); |
464 | 2.00k | if (status != 0) { |
465 | 0 | SCHECK(WIFEXITED(status), InternalError, |
466 | 0 | Format("$0 did not exit normally", initdb_program_path)); |
467 | 0 | return STATUS_FORMAT(RuntimeError, "$0 failed with exit code $1", |
468 | 0 | initdb_program_path, |
469 | 0 | WEXITSTATUS(status)); |
470 | 0 | } |
471 | | |
472 | 2.00k | LOG(INFO) << "initdb completed successfully. Database initialized at " << conf_.data_dir; |
473 | 2.00k | return Status::OK(); |
474 | 2.00k | } |
475 | | |
476 | 2.01k | Status PgWrapper::InitDbLocalOnlyIfNeeded() { |
477 | 2.01k | if (Env::Default()->FileExists(conf_.data_dir)) { |
478 | 4 | LOG(INFO) << "Data directory " << conf_.data_dir << " already exists, skipping initdb"; |
479 | 4 | return Status::OK(); |
480 | 4 | } |
481 | | // Do not communicate with the YugaByte cluster at all. This function is only concerned with |
482 | | // setting up the local PostgreSQL data directory on this tablet server. |
483 | 2.00k | return InitDb(/* yb_enabled */ false); |
484 | 2.01k | } |
485 | | |
486 | 1.99k | Result<int> PgWrapper::Wait() { |
487 | 1.99k | if (!pg_proc_) { |
488 | 0 | return STATUS(IllegalState, |
489 | 0 | "PostgreSQL child process has not been started, cannot wait for it to exit"); |
490 | 0 | } |
491 | 1.99k | return pg_proc_->Wait(); |
492 | 1.99k | } |
493 | | |
494 | | Status PgWrapper::InitDbForYSQL( |
495 | | const string& master_addresses, const string& tmp_dir_base, |
496 | 2 | int tserver_shm_fd) { |
497 | 2 | LOG(INFO) << "Running initdb to initialize YSQL cluster with master addresses " |
498 | 2 | << master_addresses; |
499 | 2 | PgProcessConf conf; |
500 | 2 | conf.master_addresses = master_addresses; |
501 | 2 | conf.pg_port = 0; // We should not use this port. |
502 | 2 | std::mt19937 rng{std::random_device()()}; |
503 | 2 | conf.data_dir = Format("$0/tmp_pg_data_$1", tmp_dir_base, rng()); |
504 | 2 | conf.tserver_shm_fd = tserver_shm_fd; |
505 | 2 | auto se = ScopeExit([&conf] { |
506 | 2 | auto is_dir = Env::Default()->IsDirectory(conf.data_dir); |
507 | 2 | if (is_dir.ok()) { |
508 | 2 | if (is_dir.get()) { |
509 | 2 | Status del_status = Env::Default()->DeleteRecursively(conf.data_dir); |
510 | 2 | if (!del_status.ok()) { |
511 | 0 | LOG(WARNING) << "Failed to delete directory " << conf.data_dir; |
512 | 0 | } |
513 | 2 | } |
514 | 2 | } else if (0 !is_dir.status().IsNotFound()0 ) { |
515 | 0 | LOG(WARNING) << "Failed to check directory existence for " << conf.data_dir << ": " |
516 | 0 | << is_dir.status(); |
517 | 0 | } |
518 | 2 | }); |
519 | 2 | PgWrapper pg_wrapper(conf); |
520 | 2 | auto start_time = std::chrono::steady_clock::now(); |
521 | 2 | Status initdb_status = pg_wrapper.InitDb(/* yb_enabled */ true); |
522 | 2 | auto elapsed_time = std::chrono::steady_clock::now() - start_time; |
523 | 2 | LOG(INFO) |
524 | 2 | << "initdb took " |
525 | 2 | << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time).count() << " ms"; |
526 | 2 | if (!initdb_status.ok()) { |
527 | 0 | LOG(ERROR) << "initdb failed: " << initdb_status; |
528 | 0 | } |
529 | 2 | return initdb_status; |
530 | 2 | } |
531 | | |
532 | 4.01k | string PgWrapper::GetPostgresExecutablePath() { |
533 | 4.01k | return JoinPathSegments(GetPostgresInstallRoot(), "bin", "postgres"); |
534 | 4.01k | } |
535 | | |
536 | 1.99k | string PgWrapper::GetPostgresLibPath() { |
537 | 1.99k | return JoinPathSegments(GetPostgresInstallRoot(), "lib"); |
538 | 1.99k | } |
539 | | |
540 | 1.99k | string PgWrapper::GetPostgresThirdPartyLibPath() { |
541 | 1.99k | return JoinPathSegments(GetPostgresInstallRoot(), "..", "lib", "yb-thirdparty"); |
542 | 1.99k | } |
543 | | |
544 | 4.02k | string PgWrapper::GetInitDbExecutablePath() { |
545 | 4.02k | return JoinPathSegments(GetPostgresInstallRoot(), "bin", "initdb"); |
546 | 4.02k | } |
547 | | |
548 | 8.03k | Status PgWrapper::CheckExecutableValid(const std::string& executable_path) { |
549 | 8.03k | if (VERIFY_RESULT(Env::Default()->IsExecutableFile(executable_path))) { |
550 | 8.03k | return Status::OK(); |
551 | 8.03k | } |
552 | 0 | return STATUS_FORMAT(NotFound, "Not an executable file: $0", executable_path); |
553 | 8.03k | } |
554 | | |
555 | 4.00k | void PgWrapper::SetCommonEnv(Subprocess* proc, bool yb_enabled) { |
556 | | // Used to resolve relative paths during YB init within PG code. |
557 | | // Needed because PG changes its current working dir to a data dir. |
558 | 4.00k | char cwd[PATH_MAX]; |
559 | 4.00k | CHECK(getcwd(cwd, sizeof(cwd)) != nullptr); |
560 | 4.00k | proc->SetEnv("YB_WORKING_DIR", cwd); |
561 | | // A temporary workaround for a failure to look up a user name by uid in an LDAP environment. |
562 | 4.00k | proc->SetEnv("YB_PG_FALLBACK_SYSTEM_USER_NAME", "postgres"); |
563 | 4.00k | proc->SetEnv("YB_PG_ALLOW_RUNNING_AS_ANY_USER", "1"); |
564 | 4.00k | proc->SetEnv("FLAGS_pggate_tserver_shm_fd", std::to_string(conf_.tserver_shm_fd)); |
565 | 4.00k | if (yb_enabled) { |
566 | 2.00k | proc->SetEnv("YB_ENABLED_IN_POSTGRES", "1"); |
567 | 2.00k | proc->SetEnv("FLAGS_pggate_master_addresses", conf_.master_addresses); |
568 | | // Postgres process can't compute default certs dir by itself |
569 | | // as it knows nothing about t-server's root data directory. |
570 | | // Solution is to specify it explicitly. |
571 | 2.00k | proc->SetEnv("FLAGS_certs_dir", conf_.certs_dir); |
572 | 2.00k | proc->SetEnv("FLAGS_certs_for_client_dir", conf_.certs_for_client_dir); |
573 | | |
574 | 2.00k | proc->SetEnv("YB_PG_TRANSACTIONS_ENABLED", FLAGS_pg_transactions_enabled ? "1" : "0"0 ); |
575 | | |
576 | | #ifdef ADDRESS_SANITIZER |
577 | | // Disable reporting signal-unsafe behavior for PostgreSQL because it does a lot of work in |
578 | | // signal handlers on shutdown. |
579 | | |
580 | | const char* asan_options = getenv("ASAN_OPTIONS"); |
581 | | proc->SetEnv( |
582 | | "ASAN_OPTIONS", |
583 | | std::string(asan_options ? asan_options : "") + " report_signal_unsafe=0"); |
584 | | #endif |
585 | | |
586 | | // Pass non-default flags to the child process using FLAGS_... environment variables. |
587 | 2.00k | static const std::vector<string> explicit_flags{"pggate_master_addresses", |
588 | 2.00k | "pggate_tserver_shm_fd", |
589 | 2.00k | "certs_dir", |
590 | 2.00k | "certs_for_client_dir"}; |
591 | 2.00k | std::vector<google::CommandLineFlagInfo> flag_infos; |
592 | 2.00k | google::GetAllFlags(&flag_infos); |
593 | 1.85M | for (const auto& flag_info : flag_infos) { |
594 | | // Skip the flags that we set explicitly using conf_ above. |
595 | 1.85M | if (!flag_info.is_default && |
596 | 1.85M | std::find(explicit_flags.begin(), |
597 | 89.3k | explicit_flags.end(), |
598 | 89.3k | flag_info.name) == explicit_flags.end()) { |
599 | 89.3k | proc->SetEnv("FLAGS_" + flag_info.name, flag_info.current_value); |
600 | 89.3k | } |
601 | 1.85M | } |
602 | 2.00k | } else { |
603 | 2.00k | proc->SetEnv("YB_PG_LOCAL_NODE_INITDB", "1"); |
604 | 2.00k | } |
605 | 4.00k | } |
606 | | |
607 | | // ------------------------------------------------------------------------------------------------ |
608 | | // PgSupervisor: monitoring a PostgreSQL child process and restarting if needed |
609 | | // ------------------------------------------------------------------------------------------------ |
610 | | |
611 | | PgSupervisor::PgSupervisor(PgProcessConf conf) |
612 | 1.99k | : conf_(std::move(conf)) { |
613 | 1.99k | } |
614 | | |
615 | 0 | PgSupervisor::~PgSupervisor() { |
616 | 0 | } |
617 | | |
618 | 1.99k | Status PgSupervisor::Start() { |
619 | 1.99k | std::lock_guard<std::mutex> lock(mtx_); |
620 | 1.99k | RETURN_NOT_OK(ExpectStateUnlocked(PgProcessState::kNotStarted)); |
621 | 1.99k | RETURN_NOT_OK(CleanupOldServerUnlocked()); |
622 | 1.99k | LOG(INFO) << "Starting PostgreSQL server"; |
623 | 1.99k | RETURN_NOT_OK(StartServerUnlocked()); |
624 | | |
625 | 1.99k | Status status = Thread::Create( |
626 | 1.99k | "pg_supervisor", "pg_supervisor", &PgSupervisor::RunThread, this, &supervisor_thread_); |
627 | 1.99k | if (!status.ok()) { |
628 | 0 | supervisor_thread_.reset(); |
629 | 0 | return status; |
630 | 0 | } |
631 | | |
632 | 1.99k | state_ = PgProcessState::kRunning; |
633 | | |
634 | 1.99k | return Status::OK(); |
635 | 1.99k | } |
636 | | |
637 | 1.99k | CHECKED_STATUS PgSupervisor::CleanupOldServerUnlocked() { |
638 | 1.99k | std::string postmaster_pid_filename = JoinPathSegments(conf_.data_dir, "postmaster.pid"); |
639 | 1.99k | if (Env::Default()->FileExists(postmaster_pid_filename)) { |
640 | 3 | std::ifstream postmaster_pid_file; |
641 | 3 | postmaster_pid_file.open(postmaster_pid_filename, std::ios_base::in); |
642 | 3 | pid_t postgres_pid = 0; |
643 | | |
644 | 3 | if (!postmaster_pid_file.eof()) { |
645 | 3 | postmaster_pid_file >> postgres_pid; |
646 | 3 | } |
647 | | |
648 | 3 | if (!postmaster_pid_file.good() || postgres_pid == 01 ) { |
649 | 2 | LOG(ERROR) << strings::Substitute("Error reading postgres process ID from file $0. $1 $2", |
650 | 2 | postmaster_pid_filename, ErrnoToString(errno), errno); |
651 | 2 | } else { |
652 | 1 | LOG(WARNING) << "Killing older postgres process: " << postgres_pid; |
653 | | // If process does not exist, system may return "process does not exist" or |
654 | | // "operation not permitted" error. Ignore those errors. |
655 | 1 | postmaster_pid_file.close(); |
656 | 1 | bool postgres_found = true; |
657 | 1 | string cmdline = ""; |
658 | | #ifdef __linux__ |
659 | | string cmd_filename = "/proc/" + std::to_string(postgres_pid) + "/cmdline"; |
660 | | std::ifstream postmaster_cmd_file; |
661 | | postmaster_cmd_file.open(cmd_filename, std::ios_base::in); |
662 | | if (postmaster_cmd_file.good()) { |
663 | | postmaster_cmd_file >> cmdline; |
664 | | postgres_found = cmdline.find("/postgres") != std::string::npos; |
665 | | postmaster_cmd_file.close(); |
666 | | } |
667 | | #endif |
668 | 1 | if (postgres_found) { |
669 | 1 | if (kill(postgres_pid, SIGKILL) != 0 && errno != ESRCH0 && errno != EPERM0 ) { |
670 | 0 | return STATUS(RuntimeError, "Unable to kill", Errno(errno)); |
671 | 0 | } |
672 | 1 | } else { |
673 | 0 | LOG(WARNING) << "Didn't find postgres in " << cmdline; |
674 | 0 | } |
675 | 1 | } |
676 | 3 | WARN_NOT_OK(Env::Default()->DeleteFile(postmaster_pid_filename), |
677 | 3 | "Failed to remove postmaster pid file"); |
678 | 3 | } |
679 | 1.99k | return Status::OK(); |
680 | 1.99k | } |
681 | | |
682 | 0 | PgProcessState PgSupervisor::GetState() { |
683 | 0 | std::lock_guard<std::mutex> lock(mtx_); |
684 | 0 | return state_; |
685 | 0 | } |
686 | | |
687 | 1.99k | CHECKED_STATUS PgSupervisor::ExpectStateUnlocked(PgProcessState expected_state) { |
688 | 1.99k | if (state_ != expected_state) { |
689 | 0 | return STATUS_FORMAT( |
690 | 0 | IllegalState, "Expected PostgreSQL server state to be $0, got $1", expected_state, state_); |
691 | 0 | } |
692 | 1.99k | return Status::OK(); |
693 | 1.99k | } |
694 | | |
695 | 2.00k | CHECKED_STATUS PgSupervisor::StartServerUnlocked() { |
696 | 2.00k | if (pg_wrapper_) { |
697 | 0 | return STATUS(IllegalState, "Expecting pg_wrapper_ to not be set"); |
698 | 0 | } |
699 | 2.00k | pg_wrapper_.emplace(conf_); |
700 | 2.00k | auto start_status = pg_wrapper_->Start(); |
701 | 2.00k | if (!start_status.ok()) { |
702 | 0 | pg_wrapper_.reset(); |
703 | 0 | return start_status; |
704 | 0 | } |
705 | 2.00k | return Status::OK(); |
706 | 2.00k | } |
707 | | |
708 | 1.99k | void PgSupervisor::RunThread() { |
709 | 3.99k | while (true) { |
710 | 1.99k | Result<int> wait_result = pg_wrapper_->Wait(); |
711 | 1.99k | if (wait_result.ok()) { |
712 | 6 | int ret_code = *wait_result; |
713 | 6 | if (ret_code == 0) { |
714 | 3 | LOG(INFO) << "PostgreSQL server exited normally"; |
715 | 3 | } else { |
716 | 3 | LOG(WARNING) << "PostgreSQL server exited with code " << ret_code; |
717 | 3 | } |
718 | 6 | pg_wrapper_.reset(); |
719 | 1.99k | } else { |
720 | | // TODO: a better way to handle this error. |
721 | 1.99k | LOG(WARNING) << "Failed when waiting for PostgreSQL server to exit: " |
722 | 1.99k | << wait_result.status() << ", waiting a bit"; |
723 | 1.99k | std::this_thread::sleep_for(1s); |
724 | 1.99k | continue; |
725 | 1.99k | } |
726 | | |
727 | 6 | { |
728 | 6 | std::lock_guard<std::mutex> lock(mtx_); |
729 | 6 | if (state_ == PgProcessState::kStopping) { |
730 | 0 | break; |
731 | 0 | } |
732 | 6 | LOG(INFO) << "Restarting PostgreSQL server"; |
733 | 6 | Status start_status = StartServerUnlocked(); |
734 | 6 | if (!start_status.ok()) { |
735 | | // TODO: a better way to handle this error. |
736 | 0 | LOG(WARNING) << "Failed trying to start PostgreSQL server: " |
737 | 0 | << start_status << ", waiting a bit"; |
738 | 0 | std::this_thread::sleep_for(1s); |
739 | 0 | } |
740 | 6 | } |
741 | 6 | } |
742 | 1.99k | } |
743 | | |
744 | 0 | void PgSupervisor::Stop() { |
745 | 0 | { |
746 | 0 | std::lock_guard<std::mutex> lock(mtx_); |
747 | 0 | state_ = PgProcessState::kStopping; |
748 | 0 | if (pg_wrapper_) { |
749 | 0 | pg_wrapper_->Kill(); |
750 | 0 | } |
751 | 0 | } |
752 | 0 | supervisor_thread_->Join(); |
753 | 0 | } |
754 | | |
755 | | } // namespace pgwrapper |
756 | | } // namespace yb |