YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/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