Files
patroni/helpers/postgresql.py
T
Oleksii Kliukin 8184efbf45 Add an option to use AWS instance address in a PostgreSQL connection string.
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.
2015-04-29 12:54:29 +02:00

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]