YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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
1.80k
Status WriteConfigFile(const string& path, const vector<string>& lines) {
106
1.80k
  std::ofstream conf_file;
107
1.80k
  conf_file.open(path, std::ios_base::out | std::ios_base::trunc);
108
1.80k
  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
1.80k
  conf_file << "# This is an autogenerated file, do not edit manually!" << std::endl;
118
641k
  for (const auto& line : lines) {
119
641k
    conf_file << line << std::endl;
120
641k
  }
121
122
1.80k
  conf_file.close();
123
124
1.80k
  return Status::OK();
125
1.80k
}
126
127
149
void ReadCommaSeparatedValues(const string& src, vector<string>* lines) {
128
149
  vector<string> new_lines;
129
149
  boost::split(new_lines, src, boost::is_any_of(","));
130
149
  lines->insert(lines->end(), new_lines.begin(), new_lines.end());
131
149
}
132
133
0
void MergeSharedPreloadLibraries(const string& src, vector<string>* defaults) {
134
0
  string copy = boost::replace_all_copy(src, " ", "");
135
0
  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
0
  copy = boost::erase_first_copy(copy, "=");
139
0
  copy = boost::trim_copy_if(copy, boost::is_any_of("'\""));
140
0
  vector<string> new_items;
141
0
  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
0
  new_items.erase(
153
0
    std::remove_if(new_items.begin(),
154
0
      new_items.end(),
155
0
      [](const std::string& s){return s.empty();}),
156
0
      new_items.end());
157
0
  defaults->insert(defaults->end(), new_items.begin(), new_items.end());
158
0
}
159
160
3
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
3
  const std::regex exp(R"(^(?:([^,"]+)|(?:"((?:[^"]|(?:""))*)\"))(?:(?:,)|(?:$)))");
174
3
  auto i = csv.begin();
175
3
  const auto end = csv.end();
176
3
  std::smatch match;
177
8
  while (i != end && std::regex_search(i, end, match, exp)) {
178
    // Replace pair of double-quote ("") with single double-quote (") in quoted field.
179
5
    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
5
    } else {
183
5
      lines->emplace_back(match[1].first, match[1].second);
184
5
    }
185
5
    i += match.length();
186
5
  }
187
3
  SCHECK(i == end, InvalidArgument, Format("Malformed CSV '$0'", csv));
188
3
  if (!csv.empty() && csv.back() == ',') {
189
0
    lines->emplace_back();
190
0
  }
191
3
  return Status::OK();
192
3
}
193
194
907
Result<string> WritePostgresConfig(const PgProcessConf& conf) {
195
  // First add default configuration created by local initdb.
196
907
  string default_conf_path = JoinPathSegments(conf.data_dir, "postgresql.conf");
197
907
  std::ifstream conf_file;
198
907
  conf_file.open(default_conf_path, std::ios_base::in);
199
907
  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
904
  vector<string> metricsLibs;
210
904
  if (FLAGS_pg_stat_statements_enabled) {
211
904
    metricsLibs.push_back("pg_stat_statements");
212
904
  }
213
904
  metricsLibs.push_back("yb_pg_metrics");
214
904
  metricsLibs.push_back("pgaudit");
215
904
  metricsLibs.push_back("pg_hint_plan");
216
217
904
  vector<string> lines;
218
904
  string line;
219
638k
  while (std::getline(conf_file, line)) {
220
637k
    lines.push_back(line);
221
637k
  }
222
904
  conf_file.close();
223
224
904
  vector<string> user_configs;
225
904
  if (!FLAGS_ysql_pg_conf_csv.empty()) {
226
2
    RETURN_NOT_OK(ReadCSVValues(FLAGS_ysql_pg_conf_csv, &user_configs));
227
902
  } else if (!FLAGS_ysql_pg_conf.empty()) {
228
5
    ReadCommaSeparatedValues(FLAGS_ysql_pg_conf, &user_configs);
229
5
  }
230
231
  // If the user has given any shared_preload_libraries, merge them in.
232
904
  for (string &value : user_configs) {
233
12
    if (boost::starts_with(value, "shared_preload_libraries")) {
234
0
      MergeSharedPreloadLibraries(value, &metricsLibs);
235
12
    } else {
236
12
      lines.push_back(value);
237
12
    }
238
12
  }
239
240
  // Add shared_preload_libraries to the ysql_pg.conf.
241
904
  lines.push_back(Format("shared_preload_libraries='$0'", boost::join(metricsLibs, ",")));
242
243
904
  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
904
  if (!FLAGS_ysql_timezone.empty()) {
255
1
    lines.push_back("timezone=" + FLAGS_ysql_timezone);
256
1
  }
257
258
904
  if (!FLAGS_ysql_datestyle.empty()) {
259
3
    lines.push_back("datestyle=" + FLAGS_ysql_datestyle);
260
3
  }
261
262
904
  if (FLAGS_ysql_max_connections > 0) {
263
4
    lines.push_back("max_connections=" + std::to_string(FLAGS_ysql_max_connections));
264
4
  }
265
266
904
  if (!FLAGS_ysql_default_transaction_isolation.empty()) {
267
2
    lines.push_back("default_transaction_isolation=" + FLAGS_ysql_default_transaction_isolation);
268
2
  }
269
270
904
  if (!FLAGS_ysql_log_statement.empty()) {
271
2
    lines.push_back("log_statement=" + FLAGS_ysql_log_statement);
272
2
  }
273
274
904
  if (!FLAGS_ysql_log_min_messages.empty()) {
275
2
    lines.push_back("log_min_messages=" + FLAGS_ysql_log_min_messages);
276
2
  }
277
278
904
  if (!FLAGS_ysql_log_min_duration_statement.empty()) {
279
2
    lines.push_back("log_min_duration_statement=" + FLAGS_ysql_log_min_duration_statement);
280
2
  }
281
282
904
  string conf_path = JoinPathSegments(conf.data_dir, "ysql_pg.conf");
283
904
  RETURN_NOT_OK(WriteConfigFile(conf_path, lines));
284
904
  return "config_file=" + conf_path;
285
904
}
286
287
904
Result<string> WritePgHbaConfig(const PgProcessConf& conf) {
288
904
  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
904
  if (!FLAGS_ysql_hba_conf_csv.empty()) {
293
1
    RETURN_NOT_OK(ReadCSVValues(FLAGS_ysql_hba_conf_csv, &lines));
294
903
  } else if (!FLAGS_ysql_hba_conf.empty()) {
295
144
    ReadCommaSeparatedValues(FLAGS_ysql_hba_conf, &lines);
296
144
  }
297
298
  // Add auto-generated config for the enable auth and enable_tls flags.
299
904
  if (FLAGS_ysql_enable_auth || conf.enable_tls) {
300
11
    const auto host_type =  conf.enable_tls ? "hostssl" : "host";
301
10
    const auto auth_method = FLAGS_ysql_enable_auth ? "md5" : "trust";
302
13
    lines.push_back(Format("$0 all all all $1", host_type, auth_method));
303
13
  }
304
305
  // Enforce a default hba configuration so users don't lock themselves out.
306
904
  if (lines.empty()) {
307
748
    LOG(WARNING) << "No hba configuration lines found, defaulting to trust all configuration.";
308
748
    lines.push_back("host all all all trust");
309
748
  }
310
311
  // Add comments to the hba config file noting the internally hardcoded config line.
312
904
  lines.insert(lines.begin(), {
313
904
      "# Internal configuration:",
314
904
      "# local all postgres yb-tserver-key",
315
904
  });
316
317
904
  const auto conf_path = JoinPathSegments(conf.data_dir, "ysql_hba.conf");
318
904
  RETURN_NOT_OK(WriteConfigFile(conf_path, lines));
319
904
  return "hba_file=" + conf_path;
320
904
}
321
322
907
Result<vector<string>> WritePgConfigFiles(const PgProcessConf& conf) {
323
907
  vector<string> args;
324
907
  args.push_back("-c");
325
904
  args.push_back(VERIFY_RESULT_PREPEND(WritePostgresConfig(conf),
326
904
      "Failed to write ysql pg configuration: "));
327
904
  args.push_back("-c");
328
904
  args.push_back(VERIFY_RESULT_PREPEND(WritePgHbaConfig(conf),
329
904
      "Failed to write ysql hba configuration: "));
330
904
  return args;
331
904
}
332
333
}  // namespace
334
335
5.43k
string GetPostgresInstallRoot() {
336
5.43k
  return JoinPathSegments(yb::env_util::GetRootDir("postgres"), "postgres");
337
5.43k
}
338
339
Result<PgProcessConf> PgProcessConf::CreateValidateAndRunInitDb(
340
    const std::string& bind_addresses,
341
    const std::string& data_dir,
342
907
    const int tserver_shm_fd) {
343
907
  PgProcessConf conf;
344
907
  if (!bind_addresses.empty()) {
345
907
    auto pg_host_port = VERIFY_RESULT(HostPort::FromString(
346
907
        bind_addresses, PgProcessConf::kDefaultPort));
347
907
    conf.listen_addresses = pg_host_port.host();
348
907
    conf.pg_port = pg_host_port.port();
349
907
  }
350
907
  conf.data_dir = data_dir;
351
907
  conf.tserver_shm_fd = tserver_shm_fd;
352
907
  PgWrapper pg_wrapper(conf);
353
907
  RETURN_NOT_OK(pg_wrapper.PreflightCheck());
354
907
  RETURN_NOT_OK(pg_wrapper.InitDbLocalOnlyIfNeeded());
355
907
  return conf;
356
907
}
357
358
// ------------------------------------------------------------------------------------------------
359
// PgWrapper: managing one instance of a PostgreSQL child process
360
// ------------------------------------------------------------------------------------------------
361
362
PgWrapper::PgWrapper(PgProcessConf conf)
363
1.81k
    : conf_(std::move(conf)) {
364
1.81k
}
365
366
907
Status PgWrapper::PreflightCheck() {
367
907
  RETURN_NOT_OK(CheckExecutableValid(GetPostgresExecutablePath()));
368
907
  RETURN_NOT_OK(CheckExecutableValid(GetInitDbExecutablePath()));
369
907
  return Status::OK();
370
907
}
371
372
907
Status PgWrapper::Start() {
373
907
  auto postgres_executable = GetPostgresExecutablePath();
374
907
  RETURN_NOT_OK(CheckExecutableValid(postgres_executable));
375
907
  vector<string> argv {
376
907
    postgres_executable,
377
907
    "-D", conf_.data_dir,
378
907
    "-p", std::to_string(conf_.pg_port),
379
907
    "-h", conf_.listen_addresses,
380
907
  };
381
382
907
  bool log_to_file = !FLAGS_logtostderr && !FLAGS_log_dir.empty() && !conf_.force_disable_log_file;
383
0
  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
907
  argv.push_back("-k");
392
907
  const std::string& socket_dir = PgDeriveSocketDir(conf_.listen_addresses);
393
907
  RETURN_NOT_OK(Env::Default()->CreateDirs(socket_dir));
394
907
  argv.push_back(socket_dir);
395
396
  // Also tighten permissions on the socket.
397
907
  argv.push_back("-c");
398
907
  argv.push_back("unix_socket_permissions=0700");
399
400
907
  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
907
  argv.push_back("-c");
409
907
  argv.push_back("yb_pg_metrics.node_name=" + FLAGS_metric_node_name);
410
907
  argv.push_back("-c");
411
907
  argv.push_back("yb_pg_metrics.port=" + std::to_string(FLAGS_pgsql_proxy_webserver_port));
412
413
907
  auto config_file_args = CHECK_RESULT(WritePgConfigFiles(conf_));
414
907
  argv.insert(argv.end(), config_file_args.begin(), config_file_args.end());
415
416
907
  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
907
  pg_proc_.emplace(postgres_executable, argv);
422
907
  vector<string> ld_library_path {
423
907
    GetPostgresLibPath(),
424
907
    GetPostgresThirdPartyLibPath()
425
907
  };
426
907
  pg_proc_->SetEnv("LD_LIBRARY_PATH", boost::join(ld_library_path, ":"));
427
907
  pg_proc_->ShareParentStderr();
428
907
  pg_proc_->ShareParentStdout();
429
  // See YBSetParentDeathSignal in pg_yb_utils.c for how this is used.
430
907
  pg_proc_->SetEnv("YB_PG_PDEATHSIG", Format("$0", SIGINT));
431
907
  pg_proc_->InheritNonstandardFd(conf_.tserver_shm_fd);
432
907
  const char* llvm_profile_env_var_value = getenv("LLVM_PROFILE_FILE");
433
907
  pg_proc_->SetEnv("LLVM_PROFILE_FILE", Format("$0_postgres.%p", llvm_profile_env_var_value));
434
907
  SetCommonEnv(&pg_proc_.get(), /* yb_enabled */ true);
435
907
  RETURN_NOT_OK(pg_proc_->Start());
436
907
  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
907
  LOG(INFO) << "PostgreSQL server running as pid " << pg_proc_->pid();
441
907
  return Status::OK();
442
907
}
443
444
0
void PgWrapper::Kill() {
445
0
  WARN_NOT_OK(pg_proc_->Kill(SIGQUIT), "Kill PostgreSQL server failed");
446
0
}
447
448
907
Status PgWrapper::InitDb(bool yb_enabled) {
449
907
  const string initdb_program_path = GetInitDbExecutablePath();
450
907
  RETURN_NOT_OK(CheckExecutableValid(initdb_program_path));
451
907
  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
907
  vector<string> initdb_args { initdb_program_path, "-D", conf_.data_dir, "-U", "postgres" };
456
907
  LOG(INFO) << "Launching initdb: " << AsString(initdb_args);
457
458
907
  Subprocess initdb_subprocess(initdb_program_path, initdb_args);
459
907
  initdb_subprocess.InheritNonstandardFd(conf_.tserver_shm_fd);
460
907
  SetCommonEnv(&initdb_subprocess, yb_enabled);
461
907
  int status = 0;
462
907
  RETURN_NOT_OK(initdb_subprocess.Start());
463
907
  RETURN_NOT_OK(initdb_subprocess.Wait(&status));
464
907
  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
907
                         initdb_program_path,
469
907
                         WEXITSTATUS(status));
470
907
  }
471
472
907
  LOG(INFO) << "initdb completed successfully. Database initialized at " << conf_.data_dir;
473
907
  return Status::OK();
474
907
}
475
476
907
Status PgWrapper::InitDbLocalOnlyIfNeeded() {
477
907
  if (Env::Default()->FileExists(conf_.data_dir)) {
478
0
    LOG(INFO) << "Data directory " << conf_.data_dir << " already exists, skipping initdb";
479
0
    return Status::OK();
480
0
  }
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
907
  return InitDb(/* yb_enabled */ false);
484
907
}
485
486
904
Result<int> PgWrapper::Wait() {
487
904
  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
904
  return pg_proc_->Wait();
492
904
}
493
494
Status PgWrapper::InitDbForYSQL(
495
    const string& master_addresses, const string& tmp_dir_base,
496
0
    int tserver_shm_fd) {
497
0
  LOG(INFO) << "Running initdb to initialize YSQL cluster with master addresses "
498
0
            << master_addresses;
499
0
  PgProcessConf conf;
500
0
  conf.master_addresses = master_addresses;
501
0
  conf.pg_port = 0;  // We should not use this port.
502
0
  std::mt19937 rng{std::random_device()()};
503
0
  conf.data_dir = Format("$0/tmp_pg_data_$1", tmp_dir_base, rng());
504
0
  conf.tserver_shm_fd = tserver_shm_fd;
505
0
  auto se = ScopeExit([&conf] {
506
0
    auto is_dir = Env::Default()->IsDirectory(conf.data_dir);
507
0
    if (is_dir.ok()) {
508
0
      if (is_dir.get()) {
509
0
        Status del_status = Env::Default()->DeleteRecursively(conf.data_dir);
510
0
        if (!del_status.ok()) {
511
0
          LOG(WARNING) << "Failed to delete directory " << conf.data_dir;
512
0
        }
513
0
      }
514
0
    } else if (!is_dir.status().IsNotFound()) {
515
0
      LOG(WARNING) << "Failed to check directory existence for " << conf.data_dir << ": "
516
0
                   << is_dir.status();
517
0
    }
518
0
  });
519
0
  PgWrapper pg_wrapper(conf);
520
0
  auto start_time = std::chrono::steady_clock::now();
521
0
  Status initdb_status = pg_wrapper.InitDb(/* yb_enabled */ true);
522
0
  auto elapsed_time = std::chrono::steady_clock::now() - start_time;
523
0
  LOG(INFO)
524
0
      << "initdb took "
525
0
      << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time).count() << " ms";
526
0
  if (!initdb_status.ok()) {
527
0
    LOG(ERROR) << "initdb failed: " << initdb_status;
528
0
  }
529
0
  return initdb_status;
530
0
}
531
532
1.81k
string PgWrapper::GetPostgresExecutablePath() {
533
1.81k
  return JoinPathSegments(GetPostgresInstallRoot(), "bin", "postgres");
534
1.81k
}
535
536
904
string PgWrapper::GetPostgresLibPath() {
537
904
  return JoinPathSegments(GetPostgresInstallRoot(), "lib");
538
904
}
539
540
904
string PgWrapper::GetPostgresThirdPartyLibPath() {
541
904
  return JoinPathSegments(GetPostgresInstallRoot(), "..", "lib", "yb-thirdparty");
542
904
}
543
544
1.81k
string PgWrapper::GetInitDbExecutablePath() {
545
1.81k
  return JoinPathSegments(GetPostgresInstallRoot(), "bin", "initdb");
546
1.81k
}
547
548
3.62k
Status PgWrapper::CheckExecutableValid(const std::string& executable_path) {
549
3.62k
  if (VERIFY_RESULT(Env::Default()->IsExecutableFile(executable_path))) {
550
3.62k
    return Status::OK();
551
3.62k
  }
552
0
  return STATUS_FORMAT(NotFound, "Not an executable file: $0", executable_path);
553
0
}
554
555
1.81k
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
1.81k
  char cwd[PATH_MAX];
559
1.81k
  CHECK(getcwd(cwd, sizeof(cwd)) != nullptr);
560
1.81k
  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
1.81k
  proc->SetEnv("YB_PG_FALLBACK_SYSTEM_USER_NAME", "postgres");
563
1.81k
  proc->SetEnv("YB_PG_ALLOW_RUNNING_AS_ANY_USER", "1");
564
1.81k
  proc->SetEnv("FLAGS_pggate_tserver_shm_fd", std::to_string(conf_.tserver_shm_fd));
565
1.81k
  if (yb_enabled) {
566
904
    proc->SetEnv("YB_ENABLED_IN_POSTGRES", "1");
567
904
    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
904
    proc->SetEnv("FLAGS_certs_dir", conf_.certs_dir);
572
904
    proc->SetEnv("FLAGS_certs_for_client_dir", conf_.certs_for_client_dir);
573
574
904
    proc->SetEnv("YB_PG_TRANSACTIONS_ENABLED", FLAGS_pg_transactions_enabled ? "1" : "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
904
    static const std::vector<string> explicit_flags{"pggate_master_addresses",
588
904
                                                    "pggate_tserver_shm_fd",
589
904
                                                    "certs_dir",
590
904
                                                    "certs_for_client_dir"};
591
904
    std::vector<google::CommandLineFlagInfo> flag_infos;
592
904
    google::GetAllFlags(&flag_infos);
593
832k
    for (const auto& flag_info : flag_infos) {
594
      // Skip the flags that we set explicitly using conf_ above.
595
832k
      if (!flag_info.is_default &&
596
40.8k
          std::find(explicit_flags.begin(),
597
40.8k
                    explicit_flags.end(),
598
40.8k
                    flag_info.name) == explicit_flags.end()) {
599
40.8k
        proc->SetEnv("FLAGS_" + flag_info.name, flag_info.current_value);
600
40.8k
      }
601
832k
    }
602
907
  } else {
603
907
    proc->SetEnv("YB_PG_LOCAL_NODE_INITDB", "1");
604
907
  }
605
1.81k
}
606
607
// ------------------------------------------------------------------------------------------------
608
// PgSupervisor: monitoring a PostgreSQL child process and restarting if needed
609
// ------------------------------------------------------------------------------------------------
610
611
PgSupervisor::PgSupervisor(PgProcessConf conf)
612
904
    : conf_(std::move(conf)) {
613
904
}
614
615
0
PgSupervisor::~PgSupervisor() {
616
0
}
617
618
904
Status PgSupervisor::Start() {
619
904
  std::lock_guard<std::mutex> lock(mtx_);
620
904
  RETURN_NOT_OK(ExpectStateUnlocked(PgProcessState::kNotStarted));
621
904
  RETURN_NOT_OK(CleanupOldServerUnlocked());
622
904
  LOG(INFO) << "Starting PostgreSQL server";
623
904
  RETURN_NOT_OK(StartServerUnlocked());
624
625
904
  Status status = Thread::Create(
626
904
      "pg_supervisor", "pg_supervisor", &PgSupervisor::RunThread, this, &supervisor_thread_);
627
904
  if (!status.ok()) {
628
0
    supervisor_thread_.reset();
629
0
    return status;
630
0
  }
631
632
904
  state_ = PgProcessState::kRunning;
633
634
904
  return Status::OK();
635
904
}
636
637
904
CHECKED_STATUS PgSupervisor::CleanupOldServerUnlocked() {
638
904
  std::string postmaster_pid_filename = JoinPathSegments(conf_.data_dir, "postmaster.pid");
639
904
  if (Env::Default()->FileExists(postmaster_pid_filename)) {
640
0
    std::ifstream postmaster_pid_file;
641
0
    postmaster_pid_file.open(postmaster_pid_filename, std::ios_base::in);
642
0
    pid_t postgres_pid = 0;
643
644
0
    if (!postmaster_pid_file.eof()) {
645
0
      postmaster_pid_file >> postgres_pid;
646
0
    }
647
648
0
    if (!postmaster_pid_file.good() || postgres_pid == 0) {
649
0
      LOG(ERROR) << strings::Substitute("Error reading postgres process ID from file $0. $1 $2",
650
0
          postmaster_pid_filename, ErrnoToString(errno), errno);
651
0
    } else {
652
0
      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
0
      postmaster_pid_file.close();
656
0
      bool postgres_found = true;
657
0
      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
0
      if (postgres_found) {
669
0
        if (kill(postgres_pid, SIGKILL) != 0 && errno != ESRCH && errno != EPERM) {
670
0
          return STATUS(RuntimeError, "Unable to kill", Errno(errno));
671
0
        }
672
0
      } else {
673
0
        LOG(WARNING) << "Didn't find postgres in " << cmdline;
674
0
      }
675
0
    }
676
0
    WARN_NOT_OK(Env::Default()->DeleteFile(postmaster_pid_filename),
677
0
                "Failed to remove postmaster pid file");
678
0
  }
679
904
  return Status::OK();
680
904
}
681
682
0
PgProcessState PgSupervisor::GetState() {
683
0
  std::lock_guard<std::mutex> lock(mtx_);
684
0
  return state_;
685
0
}
686
687
904
CHECKED_STATUS PgSupervisor::ExpectStateUnlocked(PgProcessState expected_state) {
688
904
  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
904
  return Status::OK();
693
904
}
694
695
907
CHECKED_STATUS PgSupervisor::StartServerUnlocked() {
696
907
  if (pg_wrapper_) {
697
0
    return STATUS(IllegalState, "Expecting pg_wrapper_ to not be set");
698
0
  }
699
907
  pg_wrapper_.emplace(conf_);
700
907
  auto start_status = pg_wrapper_->Start();
701
907
  if (!start_status.ok()) {
702
0
    pg_wrapper_.reset();
703
0
    return start_status;
704
0
  }
705
907
  return Status::OK();
706
907
}
707
708
904
void PgSupervisor::RunThread() {
709
1.80k
  while (true) {
710
904
    Result<int> wait_result = pg_wrapper_->Wait();
711
904
    if (wait_result.ok()) {
712
3
      int ret_code = *wait_result;
713
3
      if (ret_code == 0) {
714
3
        LOG(INFO) << "PostgreSQL server exited normally";
715
0
      } else {
716
0
        LOG(WARNING) << "PostgreSQL server exited with code " << ret_code;
717
0
      }
718
3
      pg_wrapper_.reset();
719
901
    } else {
720
      // TODO: a better way to handle this error.
721
901
      LOG(WARNING) << "Failed when waiting for PostgreSQL server to exit: "
722
901
                   << wait_result.status() << ", waiting a bit";
723
901
      std::this_thread::sleep_for(1s);
724
901
      continue;
725
901
    }
726
727
3
    {
728
3
      std::lock_guard<std::mutex> lock(mtx_);
729
3
      if (state_ == PgProcessState::kStopping) {
730
0
        break;
731
0
      }
732
3
      LOG(INFO) << "Restarting PostgreSQL server";
733
3
      Status start_status = StartServerUnlocked();
734
3
      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
3
    }
741
3
  }
742
904
}
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