| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "kudu/postgres/mini_postgres.h" |
| |
| #include <csignal> |
| #include <ostream> |
| #include <string> |
| |
| #include "kudu/gutil/strings/numbers.h" |
| #include "kudu/gutil/strings/substitute.h" |
| #include "kudu/util/env.h" |
| #include "kudu/util/faststring.h" |
| #include "kudu/util/monotime.h" |
| #include "kudu/util/net/net_util.h" |
| #include "kudu/util/path_util.h" |
| #include "kudu/util/slice.h" |
| #include "kudu/util/status.h" |
| #include "kudu/util/subprocess.h" |
| #include "kudu/util/test_util.h" |
| |
| using std::ifstream; |
| using std::string; |
| using std::unique_ptr; |
| using strings::Substitute; |
| |
| static constexpr int kPgStartTimeoutMs = 60000; |
| |
| namespace kudu { |
| namespace postgres { |
| |
| MiniPostgres::~MiniPostgres() { |
| if (process_ && process_->IsStarted()) { |
| WARN_NOT_OK(Stop(),"Unable to stop postgres"); |
| } |
| } |
| |
| Status MiniPostgres::Start() { |
| if (process_) { |
| return Status::IllegalState("Postgres already running"); |
| } |
| Env* env = Env::Default(); |
| |
| VLOG(1) << "Starting Postgres"; |
| string pgr = pg_root(); |
| if (!env->FileExists(pgr)) { |
| // This is our first time running. Set up our directories, config files, |
| // and port. |
| LOG(INFO) << "Running initdb..."; |
| Subprocess initdb({ |
| JoinPathSegments(bin_dir_, "postgres/initdb"), |
| "-D", pgr, "-L", JoinPathSegments(bin_dir_, "postgres-share") |
| }); |
| RETURN_NOT_OK_PREPEND(initdb.Start(), "failed to start initdb"); |
| RETURN_NOT_OK_PREPEND(initdb.Wait(), "failed to wait on initdb"); |
| |
| // Postgres doesn't support binding to 0 so we need to get a random unused |
| // port and persist that to the config file. |
| if (port_ == 0) { |
| RETURN_NOT_OK(GetRandomPort(host_, &port_)); |
| } |
| RETURN_NOT_OK(CreateConfigs()); |
| } |
| |
| process_.reset(new Subprocess({ |
| JoinPathSegments(bin_dir_, "postgres/postgres"), |
| "-D", pgr})); |
| // LIBDIR needs to point to the directory containing the Postgres libraries, |
| // otherwise it defaults to /usr/lib/postgres. |
| process_->SetEnvVars({ |
| { "LIBDIR", JoinPathSegments(bin_dir_, "postgres-lib") } |
| }); |
| RETURN_NOT_OK(process_->Start()); |
| |
| Status wait = WaitForTcpBind(process_->pid(), &port_, { host_ }, |
| MonoDelta::FromMilliseconds(kPgStartTimeoutMs)); |
| if (!wait.ok()) { |
| // TODO(abukor): implement retry with a different port if it can't bind |
| WARN_NOT_OK(process_->Kill(SIGINT), "failed to send SIGINT to Postgres"); |
| return wait; |
| } |
| |
| LOG(INFO) << "Postgres bound to " << port_; |
| return WaitForReady(); |
| } |
| |
| Status MiniPostgres::Stop() { |
| if (process_) { |
| RETURN_NOT_OK(process_->KillAndWait(SIGTERM)); |
| process_.reset(); |
| } |
| return Status::OK(); |
| } |
| |
| Status MiniPostgres::AddUser(const string& user, bool super) { |
| Subprocess add({ |
| JoinPathSegments(bin_dir_, "postgres/createuser"), |
| user, |
| Substitute("--$0superuser", super ? "" : "no-"), |
| "-p", SimpleItoa(port_), |
| "-h", host_, |
| }); |
| RETURN_NOT_OK(add.Start()); |
| return add.WaitAndCheckExitCode(); |
| } |
| |
| Status MiniPostgres::CreateDb(const string& db, const string& owner) { |
| Subprocess createdb({ |
| JoinPathSegments(bin_dir_, "postgres/createdb"), |
| "-O", owner, db, |
| "-p", SimpleItoa(port_), |
| "-h", host_, |
| }); |
| RETURN_NOT_OK(createdb.Start()); |
| return createdb.WaitAndCheckExitCode(); |
| } |
| |
| Status MiniPostgres::CreateConfigs() { |
| Env* env = Env::Default(); |
| // <pg_root>/postgresql.conf is generated by initdb in a previous step. We |
| // append the port to it. |
| string config_file = JoinPathSegments(pg_root(), "postgresql.conf"); |
| faststring config; |
| ReadFileToString(env, config_file, &config); |
| config.append(Substitute("\nlisten_addresses = '$0'\nport = $1\n", host_, port_)); |
| unique_ptr<WritableFile> file; |
| RETURN_NOT_OK(env->NewWritableFile(config_file, &file)); |
| RETURN_NOT_OK(file->Append(config)); |
| return file->Close(); |
| } |
| |
| Status MiniPostgres::WaitForReady() const { |
| Status s; |
| MonoTime deadline = MonoTime::Now() + MonoDelta::FromSeconds(5); |
| while (MonoTime::Now() < deadline) { |
| Subprocess psql({ |
| JoinPathSegments(bin_dir_, "postgres/pg_isready"), |
| "-p", SimpleItoa(port_), |
| "-h", host_, |
| }); |
| RETURN_NOT_OK(psql.Start()); |
| s = psql.WaitAndCheckExitCode(); |
| if (s.ok()) { |
| return s; |
| } |
| SleepFor(MonoDelta::FromMilliseconds(100)); |
| } |
| |
| return Status::TimedOut(s.ToString()); |
| } |
| |
| } // namespace postgres |
| } // namespace kudu |