diff --git a/docs/ENVIRONMENT.rst b/docs/ENVIRONMENT.rst index 3a13194e..f94ac166 100644 --- a/docs/ENVIRONMENT.rst +++ b/docs/ENVIRONMENT.rst @@ -38,6 +38,7 @@ PostgreSQL - **PATRONI\_POSTGRESQL\_LISTEN**: IP address + port that Postgres listens to. Multiple comma-separated addresses are permitted, as long as the port component is appended after to the last one with a colon, i.e. ``listen: 127.0.0.1,127.0.0.2:5432``. Patroni will use the first address from this list to establish local connections to the PostgreSQL node. - **PATRONI\_POSTGRESQL\_CONNECT\_ADDRESS**: IP address + port through which Postgres is accessible from other nodes and applications. - **PATRONI\_POSTGRESQL\_DATA\_DIR**: The location of the Postgres data directory, either existing or to be initialized by Patroni. +- **PATRONI\_POSTGRESQL\_BIN_DIR**: Path to PostgreSQL binaries. (pg_ctl, pg_rewind, pg_basebackup, postgres) The default value is an empty string meaning that PATH environment variable will be used to find the executables. - **PATRONI\_POSTGRESQL\_PGPASS**: path to the `.pgpass `__ password file. Patroni creates this file before executing pg\_basebackup and under some other circumstances. The location must be writable by Patroni. - **PATRONI\_REPLICATION\_USERNAME**: replication username; the user will be created during initialization. Replicas will use this user to access master via streaming replication - **PATRONI\_REPLICATION\_PASSWORD**: replication password; the user will be created during initialization. diff --git a/docs/SETTINGS.rst b/docs/SETTINGS.rst index 4728dce8..24b01dd3 100644 --- a/docs/SETTINGS.rst +++ b/docs/SETTINGS.rst @@ -65,6 +65,7 @@ PostgreSQL - **connect\_address**: IP address + port through which Postgres is accessible from other nodes and applications. - **create\_replica\_methods**: an ordered list of the create methods for turning a Patroni node into a new replica. "basebackup" is the default method; other methods are assumed to refer to scripts, each of which is configured as its own config item. - **data\_dir**: The location of the Postgres data directory, either existing or to be initialized by Patroni. +- **bin\_dir**: Path to PostgreSQL binaries. (pg_ctl, pg_rewind, pg_basebackup, postgres) The default value is an empty string meaning that PATH environment variable will be used to find the executables. - **listen**: IP address + port that Postgres listens to; must be accessible from other nodes in the cluster, if you're using streaming replication. Multiple comma-separated addresses are permitted, as long as the port component is appended after to the last one with a colon, i.e. ``listen: 127.0.0.1,127.0.0.2:5432``. Patroni will use the first address from this list to establish local connections to the PostgreSQL node. - **pgpass**: path to the `.pgpass `__ password file. Patroni creates this file before executing pg\_basebackup and under some other circumstances. The location must be writable by Patroni. - **recovery\_conf**: additional configuration settings written to recovery.conf when configuring follower. diff --git a/patroni/config.py b/patroni/config.py index f09cfca4..2268a59d 100644 --- a/patroni/config.py +++ b/patroni/config.py @@ -42,6 +42,7 @@ class Config(object): 'ttl': 30, 'loop_wait': 10, 'retry_timeout': 10, 'maximum_lag_on_failover': 1048576, 'postgresql': { + 'bin_dir': '', 'use_slots': True, 'parameters': {p: v[0] for p, v in Postgresql.CMDLINE_OPTIONS.items()} } @@ -193,7 +194,7 @@ class Config(object): ret[section][param] = value _set_section_values('restapi', ['listen', 'connect_address', 'certfile', 'keyfile']) - _set_section_values('postgresql', ['listen', 'connect_address', 'data_dir', 'pgpass']) + _set_section_values('postgresql', ['listen', 'connect_address', 'data_dir', 'pgpass', 'bin_dir']) def _get_auth(name): ret = {} diff --git a/patroni/postgresql.py b/patroni/postgresql.py index 440faa4f..df2279f1 100644 --- a/patroni/postgresql.py +++ b/patroni/postgresql.py @@ -58,6 +58,7 @@ class Postgresql(object): self.config = config self.name = config['name'] self.scope = config['scope'] + self._bin_dir = config.get('bin_dir') or '' self._database = config.get('database', 'postgres') self._data_dir = config['data_dir'] self._pending_restart = False @@ -132,12 +133,16 @@ class Postgresql(object): self.connection_string = 'postgres://{0}/{1}'.format( self._connect_address or self._local_address['host'] + ':' + self._local_address['port'], self._database) + def _pgcommand(self, cmd): + """Returns path to the specified PostgreSQL command""" + return os.path.join(self._bin_dir, cmd) + def pg_ctl(self, cmd, *args, **kwargs): """Builds and executes pg_ctl command :returns: `!True` when return_code == 0, otherwise `!False`""" - pg_ctl = ['pg_ctl', cmd] + pg_ctl = [self._pgcommand('pg_ctl'), cmd] if cmd in ('start', 'stop', 'restart'): pg_ctl += ['-w'] timeout = self.config.get('pg_ctl_timeout') @@ -222,7 +227,7 @@ class Postgresql(object): if not (self.config.get('use_pg_rewind') and all(self._superuser.get(n) for n in ('username', 'password'))): return False - cmd = ['pg_rewind', '--help'] + cmd = [self._pgcommand('pg_rewind'), '--help'] try: ret = subprocess.call(cmd, stdout=open(os.devnull, 'w'), stderr=subprocess.STDOUT) if ret != 0: # pg_rewind is not there, close up the shop and go home @@ -657,7 +662,10 @@ class Postgresql(object): dsn = 'user={user} host={host} port={port} dbname={database} sslmode=prefer sslcompression=1'.format(**r) logger.info('running pg_rewind from %s', dsn) try: - return subprocess.call(['pg_rewind', '-D', self._data_dir, '--source-server', dsn], env=env) == 0 + return subprocess.call([self._pgcommand('pg_rewind'), + '-D', self._data_dir, + '--source-server', dsn, + ], env=env) == 0 except OSError: return False @@ -667,7 +675,7 @@ class Postgresql(object): # Don't try to call pg_controldata during backup restore if self._version_file_exists() and self.state != 'creating replica': try: - data = subprocess.check_output(['pg_controldata', self._data_dir]) + data = subprocess.check_output([self._pgcommand('pg_controldata'), self._data_dir]) if data: data = data.decode('utf-8').splitlines() result = {l.split(':')[0].replace('Current ', '', 1): l.split(':')[1].strip() for l in data if l} @@ -693,7 +701,7 @@ class Postgresql(object): def single_user_mode(self, command=None, options=None): """ run a given command in a single-user mode. If the command is empty - then just start and stop """ - cmd = ['postgres', '--single', '-D', self._data_dir] + cmd = [self._pgcommand('postgres'), '--single', '-D', self._data_dir] for opt, val in sorted((options or {}).items()): cmd.extend(['-c', '{0}={1}'.format(opt, val)]) # need a database name to connect @@ -965,7 +973,7 @@ $$""".format(name, ' '.join(options)), name, password, password) ret = 1 for bbfailures in range(0, maxfailures): try: - ret = subprocess.call(['pg_basebackup', '--pgdata=' + self._data_dir, + ret = subprocess.call([self._pgcommand('pg_basebackup'), '--pgdata=' + self._data_dir, '--xlog-method=stream', "--dbname=" + conn_url], env=env) if ret == 0: break diff --git a/postgres0.yml b/postgres0.yml index cb1000c5..499501bb 100644 --- a/postgres0.yml +++ b/postgres0.yml @@ -58,6 +58,7 @@ postgresql: listen: 127.0.0.1:5432 connect_address: 127.0.0.1:5432 data_dir: data/postgresql0 +# bin_dir: pgpass: /tmp/pgpass0 authentication: replication: