mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
When PostgreSQL is running inside the docker container, it should provide the address of the host it's running on and not the address from inside of the container to the outside world.
188 lines
6.8 KiB
Python
188 lines
6.8 KiB
Python
import os, psycopg2, re, time
|
|
import logging
|
|
|
|
from urlparse import urlparse
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Postgresql:
|
|
|
|
def __init__(self, config, aws_host_address=None):
|
|
self.name = config["name"]
|
|
self.host, self.port = config["listen"].split(":")
|
|
self.data_dir = config["data_dir"]
|
|
self.replication = config["replication"]
|
|
|
|
self.config = config
|
|
|
|
self.cursor_holder = None
|
|
connection_host = aws_host_address or self.host
|
|
self.connection_string = "postgres://%s:%s@%s:%s/postgres" % (self.replication["username"], self.replication["password"], connection_host, self.port)
|
|
|
|
self.conn = None
|
|
|
|
def cursor(self):
|
|
if not self.cursor_holder:
|
|
self.conn = psycopg2.connect("postgres://%s:%s/postgres" % (self.host, self.port))
|
|
self.conn.autocommit = True
|
|
self.cursor_holder = self.conn.cursor()
|
|
|
|
return self.cursor_holder
|
|
|
|
def disconnect(self):
|
|
try:
|
|
self.conn.close()
|
|
except Exception as e:
|
|
logger.error("Error disconnecting: %s" % e)
|
|
|
|
def query(self, sql):
|
|
max_attempts = 0
|
|
while True:
|
|
try:
|
|
self.cursor().execute(sql)
|
|
break
|
|
except psycopg2.OperationalError as e:
|
|
if self.conn:
|
|
self.disconnect()
|
|
self.cursor_holder = None
|
|
if max_attempts > 4:
|
|
raise e
|
|
max_attempts += 1
|
|
time.sleep(5)
|
|
return self.cursor()
|
|
|
|
def data_directory_empty(self):
|
|
return not os.path.exists(self.data_dir) or os.listdir(self.data_dir) == []
|
|
|
|
def initialize(self):
|
|
if os.system("initdb -D %s" % self.data_dir) == 0:
|
|
self.write_pg_hba()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def sync_from_leader(self, leader):
|
|
leader = urlparse(leader["address"])
|
|
|
|
f = open("./pgpass", "w")
|
|
f.write("%(hostname)s:%(port)s:*:%(username)s:%(password)s\n" %
|
|
{"hostname": leader.hostname, "port": leader.port, "username": leader.username, "password": leader.password})
|
|
f.close()
|
|
|
|
os.system("chmod 600 pgpass")
|
|
|
|
return os.system("PGPASSFILE=pgpass pg_basebackup -R -D %(data_dir)s --host=%(host)s --port=%(port)s -U %(username)s" %
|
|
{"data_dir": self.data_dir, "host": leader.hostname, "port": leader.port, "username": leader.username}) == 0
|
|
|
|
def is_leader(self):
|
|
return not self.query("SELECT pg_is_in_recovery();").fetchone()[0]
|
|
|
|
def is_running(self):
|
|
return os.system("pg_ctl status -D %s > /dev/null" % self.data_dir) == 0
|
|
|
|
def start(self):
|
|
if self.is_running():
|
|
logger.error("Cannot start PostgreSQL because one is already running.")
|
|
return False
|
|
|
|
pid_path = "%s/postmaster.pid" % self.data_dir
|
|
if os.path.exists(pid_path):
|
|
os.remove(pid_path)
|
|
logger.info("Removed %s" % pid_path)
|
|
|
|
command_code = os.system("postgres -D %s %s &" % (self.data_dir, self.server_options()))
|
|
time.sleep(5)
|
|
return command_code != 0
|
|
|
|
def stop(self):
|
|
return os.system("pg_ctl stop -w -D %s -m fast -w" % self.data_dir) != 0
|
|
|
|
def reload(self):
|
|
return os.system("pg_ctl reload -w -D %s" % self.data_dir) == 0
|
|
|
|
def restart(self):
|
|
return os.system("pg_ctl restart -w -D %s -m fast" % self.data_dir) == 0
|
|
|
|
def server_options(self):
|
|
options = "-c listen_addresses=%s -c port=%s" % (self.host, self.port)
|
|
for setting, value in self.config["parameters"].iteritems():
|
|
options += " -c \"%s=%s\"" % (setting, value)
|
|
return options
|
|
|
|
def is_healthy(self):
|
|
if not self.is_running():
|
|
logger.warning("Postgresql is not running.")
|
|
return False
|
|
|
|
return True
|
|
|
|
def is_healthiest_node(self, members):
|
|
for member in members:
|
|
if member["hostname"] == self.name:
|
|
continue
|
|
try:
|
|
member_conn = psycopg2.connect(member["address"])
|
|
member_conn.autocommit = True
|
|
member_cursor = member_conn.cursor()
|
|
member_cursor.execute("SELECT '%s'::pg_lsn - pg_last_xlog_replay_location() AS bytes;" % self.xlog_position())
|
|
xlog_diff = member_cursor.fetchone()[0]
|
|
logger.info([self.name, member["hostname"], xlog_diff])
|
|
if xlog_diff < 0:
|
|
member_cursor.close()
|
|
return False
|
|
member_cursor.close()
|
|
except psycopg2.OperationalError:
|
|
continue
|
|
return True
|
|
|
|
def replication_slot_name(self):
|
|
member = os.environ.get("MEMBER")
|
|
(member, _) = re.subn(r'[^a-z0-9]+', r'_', member)
|
|
return member
|
|
|
|
def write_pg_hba(self):
|
|
f = open("%s/pg_hba.conf" % self.data_dir, "a")
|
|
f.write("host replication %(username)s %(network)s md5" %
|
|
{"username": self.replication["username"], "network": self.replication["network"]})
|
|
# allow TCP connections from the host's own address
|
|
f.write("\nhost postgres postgres samehost trust\n" % {"network": self.host})
|
|
f.close()
|
|
|
|
def write_recovery_conf(self, leader_hash):
|
|
leader = urlparse(leader_hash["address"])
|
|
|
|
f = open("%s/recovery.conf" % self.data_dir, "w")
|
|
f.write("""
|
|
standby_mode = 'on'
|
|
primary_slot_name = '%(recovery_slot)s'
|
|
primary_conninfo = 'user=%(user)s password=%(password)s host=%(hostname)s port=%(port)s sslmode=prefer sslcompression=1'
|
|
recovery_target_timeline = 'latest'
|
|
""" % {"recovery_slot": self.name, "user": leader.username, "password": leader.password, "hostname": leader.hostname, "port": leader.port})
|
|
if "recovery_conf" in self.config:
|
|
for name, value in self.config["recovery_conf"].iteritems():
|
|
f.write("%s = '%s'" % (name, value))
|
|
f.close()
|
|
|
|
def follow_the_leader(self, leader_hash):
|
|
leader = urlparse(leader_hash["address"])
|
|
if os.system("grep 'host=%(hostname)s port=%(port)s' %(data_dir)s/recovery.conf > /dev/null" % {"hostname": leader.hostname, "port": leader.port, "data_dir": self.data_dir}) != 0:
|
|
self.write_recovery_conf(leader_hash)
|
|
self.restart()
|
|
return True
|
|
|
|
def promote(self):
|
|
return os.system("pg_ctl promote -w -D %s" % self.data_dir) == 0
|
|
|
|
def demote(self, leader):
|
|
self.write_recovery_conf(leader)
|
|
self.restart()
|
|
|
|
def create_replication_user(self):
|
|
self.query("CREATE USER \"%s\" WITH REPLICATION ENCRYPTED PASSWORD '%s';" % (self.replication["username"], self.replication["password"]))
|
|
|
|
def xlog_position(self):
|
|
return self.query("SELECT pg_last_xlog_replay_location();").fetchone()[0]
|