Moved all constants into their own pgbouncemgr.constants module.
This commit is contained in:
parent
a3a97a9721
commit
4f8f1473a1
7
Makefile
7
Makefile
|
@ -13,10 +13,13 @@ run:
|
||||||
PYTHONPATH=./ python3 pgbouncemgr/manager.py
|
PYTHONPATH=./ python3 pgbouncemgr/manager.py
|
||||||
|
|
||||||
run-d:
|
run-d:
|
||||||
PYTHONPATH=./ python3 pgbouncemgr/manager.py -d
|
PYTHONPATH=./ python3 pgbouncemgr/manager.py --debug
|
||||||
|
|
||||||
|
run-s:
|
||||||
|
PYTHONPATH=./ python3 pgbouncemgr/manager.py --debug --single-shot
|
||||||
|
|
||||||
run-v:
|
run-v:
|
||||||
PYTHONPATH=./ python3 pgbouncemgr/manager.py -v
|
PYTHONPATH=./ python3 pgbouncemgr/manager.py --verbose
|
||||||
|
|
||||||
run-h:
|
run-h:
|
||||||
PYTHONPATH=./ python3 pgbouncemgr/manager.py -h
|
PYTHONPATH=./ python3 pgbouncemgr/manager.py -h
|
||||||
|
|
|
@ -55,7 +55,7 @@ class Config():
|
||||||
"connect_timeout": 1,
|
"connect_timeout": 1,
|
||||||
"user": "pgbouncemgr",
|
"user": "pgbouncemgr",
|
||||||
"password": None,
|
"password": None,
|
||||||
"name": "template1",
|
"database": "template1",
|
||||||
}
|
}
|
||||||
self.pgbouncer = {
|
self.pgbouncer = {
|
||||||
"pgbouncer_config": "/etc/pgbouncer/pgbouncer.ini",
|
"pgbouncer_config": "/etc/pgbouncer/pgbouncer.ini",
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Constants as used by the pgbouncemgr modules."""
|
||||||
|
|
||||||
|
# Configuration defaults.
|
||||||
|
DEFAULT_CONFIG = "/etc/pgbouncer/pgbouncemgr.yaml"
|
||||||
|
DEFAULT_LOG_FACILITY = "LOG_LOCAL1"
|
||||||
|
|
||||||
|
# Return values for the PgConnection.connect() method.
|
||||||
|
CONN_CONNECTED = 'CONNECTED'
|
||||||
|
CONN_REUSED = 'REUSED'
|
||||||
|
CONN_RECONNECTED = 'RECONNECTED'
|
||||||
|
|
||||||
|
# Backend node status.
|
||||||
|
NODE_UNKNOWN = "NODE_UNKNOWN"
|
||||||
|
NODE_OFFLINE = "NODE_OFFLINE"
|
||||||
|
NODE_PRIMARY = "NODE_PRIMARY"
|
||||||
|
NODE_STANDBY = "NODE_STANDBY"
|
||||||
|
NODE_STATUSES = [NODE_UNKNOWN, NODE_OFFLINE, NODE_PRIMARY, NODE_STANDBY]
|
||||||
|
|
||||||
|
# Leader node status.
|
||||||
|
LEADER_UNKNOWN = "LEADER_UNKNOWN"
|
||||||
|
LEADER_CONNECTED = "LEADER_CONNECTED"
|
||||||
|
LEADER_DISCONNECTED = "LEADER_DISCONNECTED"
|
||||||
|
LEADER_STATUSES = [LEADER_UNKNOWN, LEADER_CONNECTED, LEADER_DISCONNECTED]
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from pgbouncemgr.constants import DEFAULT_CONFIG, DEFAULT_LOG_FACILITY
|
||||||
from pgbouncemgr.logger import Logger, ConsoleLog, SyslogLog
|
from pgbouncemgr.logger import Logger, ConsoleLog, SyslogLog
|
||||||
from pgbouncemgr.config import Config
|
from pgbouncemgr.config import Config
|
||||||
from pgbouncemgr.state import State
|
from pgbouncemgr.state import State
|
||||||
|
@ -14,14 +15,11 @@ from pgbouncemgr.state_store import StateStore
|
||||||
from pgbouncemgr.node_poller import NodePoller
|
from pgbouncemgr.node_poller import NodePoller
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = "/etc/pgbouncer/pgbouncemgr.yaml"
|
|
||||||
DEFAULT_LOG_FACILITY = "LOG_LOCAL1"
|
|
||||||
|
|
||||||
|
|
||||||
class Manager():
|
class Manager():
|
||||||
def __init__(self, argv=None):
|
def __init__(self, argv=None):
|
||||||
args = _parse_arguments(argv)
|
args = _parse_arguments(argv)
|
||||||
self.config = Config(args.config)
|
self.config = Config(args.config)
|
||||||
|
self.single_shot = args.single_shot
|
||||||
self._create_logger(args)
|
self._create_logger(args)
|
||||||
self._create_state()
|
self._create_state()
|
||||||
self.node_poller = NodePoller(self.state)
|
self.node_poller = NodePoller(self.state)
|
||||||
|
@ -42,6 +40,8 @@ class Manager():
|
||||||
drop_privileges(self.config.run_user, self.config.run_group)
|
drop_privileges(self.config.run_user, self.config.run_group)
|
||||||
while True:
|
while True:
|
||||||
self.node_poller.poll()
|
self.node_poller.poll()
|
||||||
|
if self.single_shot:
|
||||||
|
return
|
||||||
sleep(self.config.poll_interval_in_sec)
|
sleep(self.config.poll_interval_in_sec)
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +55,11 @@ def _parse_arguments(args):
|
||||||
"-d", "--debug",
|
"-d", "--debug",
|
||||||
default=False, action="store_true",
|
default=False, action="store_true",
|
||||||
help="enable debugging console output (default: disabled)")
|
help="enable debugging console output (default: disabled)")
|
||||||
|
parser.add_argument(
|
||||||
|
"-s", "--single-shot",
|
||||||
|
default=False, action="store_true",
|
||||||
|
help="do only a single run, instead of running continuously " +
|
||||||
|
"(default: disabled)")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-f", "--log-facility",
|
"-f", "--log-facility",
|
||||||
default=DEFAULT_LOG_FACILITY,
|
default=DEFAULT_LOG_FACILITY,
|
||||||
|
|
|
@ -14,7 +14,7 @@ class NodeConfig():
|
||||||
self.host = None
|
self.host = None
|
||||||
self.port = None
|
self.port = None
|
||||||
self.connect_timeout = 1
|
self.connect_timeout = 1
|
||||||
self.name = 'template1'
|
self.database = 'template1'
|
||||||
self.user = 'pgbouncemgr'
|
self.user = 'pgbouncemgr'
|
||||||
self.password = None
|
self.password = None
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,47 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# no-pylint: disable=missing-docstring,too-few-public-methods
|
# no-pylint: disable=missing-docstring,too-few-public-methods
|
||||||
|
|
||||||
|
from pgbouncemgr.postgres import PgReplicationConnection, PgException
|
||||||
|
|
||||||
class NodePoller():
|
class NodePoller():
|
||||||
"""The NodePoller is used to poll all the nodes that are available
|
"""The NodePoller is used to poll all the nodes that are available
|
||||||
in the state object, and to update their status according to
|
in the state object, and to update their status according to
|
||||||
the results."""
|
the results.
|
||||||
def __init__(self, state):
|
The connection_class that can be provided is used for testing
|
||||||
|
purposes (dependency injection)."""
|
||||||
|
def __init__(self, state, connection_class=None):
|
||||||
|
self.connection_class = PgReplicationConnection
|
||||||
|
if connection_class is not None:
|
||||||
|
self.connection_class = connection_class
|
||||||
self.state = state
|
self.state = state
|
||||||
|
self._connections = {}
|
||||||
|
|
||||||
def poll(self):
|
def poll(self):
|
||||||
|
"""Check the status of all backend nodes and update their status."""
|
||||||
for node in self.state.nodes.values():
|
for node in self.state.nodes.values():
|
||||||
print(repr(node.config))
|
self._poll_node(node)
|
||||||
|
|
||||||
|
def _poll_node(self, node):
|
||||||
|
connection = self._get_connection_object(node)
|
||||||
|
try:
|
||||||
|
result = connection.connect()
|
||||||
|
print(result)
|
||||||
|
status = connection.get_replication_status()
|
||||||
|
node.status = status["status"]
|
||||||
|
node.timeline_id = status["timeline_id"]
|
||||||
|
node.system_id = status["system_id"]
|
||||||
|
except PgException as exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _get_connection_object(self, node):
|
||||||
|
if node.node_id in self._connections:
|
||||||
|
return self._connections[node.node_id]
|
||||||
|
connection = self.connection_class(node.config)
|
||||||
|
self._connections[node.node_id] = connection
|
||||||
|
return connection
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset all database connections."""
|
||||||
|
for connection in self._connections:
|
||||||
|
connection.disconnect()
|
||||||
|
self._connections = {}
|
||||||
|
|
|
@ -8,6 +8,9 @@ import multiprocessing
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import LogicalReplicationConnection
|
from psycopg2.extras import LogicalReplicationConnection
|
||||||
from pgbouncemgr.logger import format_ex
|
from pgbouncemgr.logger import format_ex
|
||||||
|
from pgbouncemgr.constants import \
|
||||||
|
NODE_OFFLINE, NODE_STANDBY, NODE_PRIMARY,\
|
||||||
|
CONN_REUSED, CONN_RECONNECTED, CONN_CONNECTED
|
||||||
|
|
||||||
|
|
||||||
class PgException(Exception):
|
class PgException(Exception):
|
||||||
|
@ -34,14 +37,16 @@ class ConnectedToWrongBackend(PgException):
|
||||||
"backend service: %s" % msg)
|
"backend service: %s" % msg)
|
||||||
|
|
||||||
|
|
||||||
# Return values for the PgConnection.connect() method.
|
|
||||||
CONNECTED = 'CONNECTED'
|
|
||||||
REUSED = 'REUSED'
|
|
||||||
RECONNECTED = 'RECONNECTED'
|
|
||||||
|
|
||||||
# The properties that can be used in a configuration object to
|
# The properties that can be used in a configuration object to
|
||||||
# define connection parameters for psycopg2.
|
# define connection parameters for psycopg2.
|
||||||
CONNECTION_PARAMS = ['host', 'port', 'connect_timeout', 'user', 'password']
|
CONNECTION_PARAMS = [
|
||||||
|
'host',
|
||||||
|
'port',
|
||||||
|
'user',
|
||||||
|
'password',
|
||||||
|
'database',
|
||||||
|
'connect_timeout'
|
||||||
|
]
|
||||||
|
|
||||||
class PgConnection():
|
class PgConnection():
|
||||||
"""Implements a connection to a PostgreSQL server."""
|
"""Implements a connection to a PostgreSQL server."""
|
||||||
|
@ -69,20 +74,20 @@ class PgConnection():
|
||||||
this connection. If no, or when no connection exists, then
|
this connection. If no, or when no connection exists, then
|
||||||
setup a new connection.
|
setup a new connection.
|
||||||
Raises an exeption when the database connection cannot be setup.
|
Raises an exeption when the database connection cannot be setup.
|
||||||
returns CONNECTED, REUSED or RECONNECTED when the connection
|
returns CONN_CONNECTED, CONN_REUSED or CONN_RECONNECTED when
|
||||||
was setup successfully."""
|
the connection was setup successfully."""
|
||||||
reconnected = False
|
reconnected = False
|
||||||
if self.conn is not None:
|
if self.conn is not None:
|
||||||
try:
|
try:
|
||||||
with self.conn.cursor() as cursor:
|
with self.conn.cursor() as cursor:
|
||||||
cursor.execute(self.ping_query)
|
cursor.execute(self.ping_query)
|
||||||
return REUSED
|
return CONN_REUSED
|
||||||
except psycopg2.OperationalError:
|
except psycopg2.OperationalError:
|
||||||
reconnected = True
|
reconnected = True
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
try:
|
try:
|
||||||
self.conn = psycopg2.connect(**self.conn_params)
|
self.conn = psycopg2.connect(**self.conn_params)
|
||||||
return RECONNECTED if reconnected else CONNECTED
|
return CONN_RECONNECTED if reconnected else CONN_CONNECTED
|
||||||
except psycopg2.OperationalError as exception:
|
except psycopg2.OperationalError as exception:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
raise PgConnectionFailed(exception)
|
raise PgConnectionFailed(exception)
|
||||||
|
@ -97,11 +102,6 @@ class PgConnection():
|
||||||
self.conn = None
|
self.conn = None
|
||||||
|
|
||||||
|
|
||||||
# Return values for the PgReplicationConnection status.
|
|
||||||
OFFLINE = "OFFLINE"
|
|
||||||
PRIMARY = "PRIMARY"
|
|
||||||
STANDBY = "STANDBY"
|
|
||||||
|
|
||||||
class PgReplicationConnection(PgConnection):
|
class PgReplicationConnection(PgConnection):
|
||||||
"""This PostgresQL connection class is used to setup a replication
|
"""This PostgresQL connection class is used to setup a replication
|
||||||
connection to a PostgreSQL database server, which can be used
|
connection to a PostgreSQL database server, which can be used
|
||||||
|
@ -112,19 +112,20 @@ class PgReplicationConnection(PgConnection):
|
||||||
|
|
||||||
def get_replication_status(self):
|
def get_replication_status(self):
|
||||||
"""Returns the replication status for a node. This is an array,
|
"""Returns the replication status for a node. This is an array,
|
||||||
containing the keys "status" (OFFLINE, PRIMARY or STANDBY),
|
containing the keys "status" (NODE_OFFLINE, NODE_PRIMARY or
|
||||||
"system_id" and "timeline_id"."""
|
NODE_STANDBY), "system_id" and "timeline_id"."""
|
||||||
status = {
|
status = {
|
||||||
"status": None,
|
"status": None,
|
||||||
"system_id": None,
|
"system_id": None,
|
||||||
"timeline_id": None
|
"timeline_id": None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Try to connect to the node. If this fails, the node is OFFLINE.
|
# Try to connect to the node. If this fails, the status is
|
||||||
|
# set to NODE_OFFLINE.
|
||||||
try:
|
try:
|
||||||
self.connect()
|
self.connect()
|
||||||
except PgConnectionFailed:
|
except PgConnectionFailed:
|
||||||
status["status"] = OFFLINE
|
status["status"] = NODE_OFFLINE
|
||||||
return status
|
return status
|
||||||
|
|
||||||
# Check if the node is running in primary or standby mode.
|
# Check if the node is running in primary or standby mode.
|
||||||
|
@ -132,7 +133,7 @@ class PgReplicationConnection(PgConnection):
|
||||||
with self.conn.cursor() as cursor:
|
with self.conn.cursor() as cursor:
|
||||||
cursor.execute("SELECT pg_is_in_recovery()")
|
cursor.execute("SELECT pg_is_in_recovery()")
|
||||||
in_recovery = cursor.fetchone()[0]
|
in_recovery = cursor.fetchone()[0]
|
||||||
status["status"] = STANDBY if in_recovery else PRIMARY
|
status["status"] = NODE_STANDBY if in_recovery else NODE_PRIMARY
|
||||||
except psycopg2.InternalError as exception:
|
except psycopg2.InternalError as exception:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
raise RetrievingPgReplicationStatusFailed(
|
raise RetrievingPgReplicationStatusFailed(
|
||||||
|
|
|
@ -3,18 +3,9 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pgbouncemgr.node_config import NodeConfig
|
from pgbouncemgr.node_config import NodeConfig
|
||||||
|
from pgbouncemgr.constants import \
|
||||||
|
LEADER_UNKNOWN, LEADER_STATUSES,\
|
||||||
LEADER_UNKNOWN = "LEADER_UNKNOWN"
|
NODE_UNKNOWN, NODE_OFFLINE, NODE_PRIMARY, NODE_STANDBY, NODE_STATUSES
|
||||||
LEADER_CONNECTED = "LEADER_CONNECTED"
|
|
||||||
LEADER_DISCONNECTED = "LEADER_DISCONNECTED"
|
|
||||||
LEADER_STATUSES = [LEADER_UNKNOWN, LEADER_CONNECTED, LEADER_DISCONNECTED]
|
|
||||||
|
|
||||||
NODE_UNKNOWN = "NODE_UNKNOWN"
|
|
||||||
NODE_OFFLINE = "NODE_OFFLINE"
|
|
||||||
NODE_PRIMARY = "NODE_PRIMARY"
|
|
||||||
NODE_STANDBY = "NODE_STANDBY"
|
|
||||||
NODE_STATUSES = [NODE_UNKNOWN, NODE_OFFLINE, NODE_PRIMARY, NODE_STANDBY]
|
|
||||||
|
|
||||||
|
|
||||||
class StateException(Exception):
|
class StateException(Exception):
|
||||||
|
|
|
@ -52,7 +52,7 @@ class ConfigTests(unittest.TestCase):
|
||||||
self.assertEqual(1, config.db_connection_defaults["connect_timeout"])
|
self.assertEqual(1, config.db_connection_defaults["connect_timeout"])
|
||||||
self.assertEqual("pgbouncemgr", config.db_connection_defaults["user"])
|
self.assertEqual("pgbouncemgr", config.db_connection_defaults["user"])
|
||||||
self.assertEqual(None, config.db_connection_defaults["password"])
|
self.assertEqual(None, config.db_connection_defaults["password"])
|
||||||
self.assertEqual("template1", config.db_connection_defaults["name"])
|
self.assertEqual("template1", config.db_connection_defaults["database"])
|
||||||
# pgbouncer
|
# pgbouncer
|
||||||
self.assertEqual("/etc/pgbouncer/pgbouncer.ini", config.pgbouncer["pgbouncer_config"])
|
self.assertEqual("/etc/pgbouncer/pgbouncer.ini", config.pgbouncer["pgbouncer_config"])
|
||||||
self.assertEqual(6432, config.pgbouncer["port"])
|
self.assertEqual(6432, config.pgbouncer["port"])
|
||||||
|
@ -64,7 +64,7 @@ class ConfigTests(unittest.TestCase):
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
"connect_timeout": 1,
|
"connect_timeout": 1,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"name": "template1",
|
"database": "template1",
|
||||||
"password": "Wilmaaaaa!!!",
|
"password": "Wilmaaaaa!!!",
|
||||||
"pgbouncer_config": "/etc/pgbouncer/pgbouncer.ini",
|
"pgbouncer_config": "/etc/pgbouncer/pgbouncer.ini",
|
||||||
"port": 6432,
|
"port": 6432,
|
||||||
|
@ -74,7 +74,7 @@ class ConfigTests(unittest.TestCase):
|
||||||
"nodeA": {
|
"nodeA": {
|
||||||
"connect_timeout": 1,
|
"connect_timeout": 1,
|
||||||
"host": "1.2.3.4",
|
"host": "1.2.3.4",
|
||||||
"name": "template1",
|
"database": "template1",
|
||||||
"password": "Wilmaaaaa!!!",
|
"password": "Wilmaaaaa!!!",
|
||||||
"port": 8888,
|
"port": 8888,
|
||||||
"user": "pgbouncemgr"
|
"user": "pgbouncemgr"
|
||||||
|
@ -82,7 +82,7 @@ class ConfigTests(unittest.TestCase):
|
||||||
"nodeB": {
|
"nodeB": {
|
||||||
"connect_timeout": 1,
|
"connect_timeout": 1,
|
||||||
"host": "2.3.4.5",
|
"host": "2.3.4.5",
|
||||||
"name": "template1",
|
"database": "template1",
|
||||||
"password": "Wilmaaaaa!!!",
|
"password": "Wilmaaaaa!!!",
|
||||||
"port": 7777,
|
"port": 7777,
|
||||||
"user": "pgbouncemgr"
|
"user": "pgbouncemgr"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import unittest
|
import unittest
|
||||||
from pgbouncemgr.logger import *
|
from pgbouncemgr.logger import *
|
||||||
from pgbouncemgr.state import *
|
from pgbouncemgr.state import *
|
||||||
|
from pgbouncemgr.constants import LEADER_UNKNOWN, LEADER_CONNECTED
|
||||||
|
|
||||||
|
|
||||||
PGBOUNCER_CONFIG = os.path.join(
|
PGBOUNCER_CONFIG = os.path.join(
|
||||||
|
|
Loading…
Reference in New Issue