Completed the postgres module tests.
This commit is contained in:
parent
475e20a588
commit
5e84e88bc1
|
@ -240,8 +240,8 @@ class PgConnectionViaPgbouncer(PgConnection):
|
||||||
# allows for doing some extra checks.
|
# allows for doing some extra checks.
|
||||||
|
|
||||||
def verify_connection(self):
|
def verify_connection(self):
|
||||||
"""Check if the connection via pgbouncer ends up with the
|
"""This function checks if the connection via pgbouncer ends up at
|
||||||
configured node."""
|
the currently known primary node."""
|
||||||
# This is done in a somewhat convoluted way with a subprocess and a
|
# This is done in a somewhat convoluted way with a subprocess and a
|
||||||
# timer. This is done, because a connection is made through
|
# timer. This is done, because a connection is made through
|
||||||
# pgbouncer, and pgbouncer will try really hard to connect the user
|
# pgbouncer, and pgbouncer will try really hard to connect the user
|
||||||
|
@ -258,10 +258,7 @@ class PgConnectionViaPgbouncer(PgConnection):
|
||||||
# we're going for HA here."""
|
# we're going for HA here."""
|
||||||
def check_func(report_func):
|
def check_func(report_func):
|
||||||
try:
|
try:
|
||||||
# Setup the database connection
|
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
# Check if we're connected to the requested node.
|
|
||||||
with self.conn.cursor() as cursor:
|
with self.conn.cursor() as cursor:
|
||||||
try:
|
try:
|
||||||
cursor.execute(VERIFY_QUERY, {
|
cursor.execute(VERIFY_QUERY, {
|
||||||
|
@ -269,28 +266,47 @@ class PgConnectionViaPgbouncer(PgConnection):
|
||||||
"port": self.node_config.port
|
"port": self.node_config.port
|
||||||
})
|
})
|
||||||
result = cursor.fetchone()[0]
|
result = cursor.fetchone()[0]
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
if result is not None:
|
|
||||||
return report_func(
|
|
||||||
False,
|
|
||||||
"The pgbouncer is not connected to the expected PostgreSQL " +
|
|
||||||
"backend service: %s" % result,
|
|
||||||
cursor)
|
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
return report_func(False, format_ex(exception), cursor)
|
return report_func(
|
||||||
|
False,
|
||||||
|
"Error running query: %s %s" %
|
||||||
|
(repr(cursor), format_ex(exception)),
|
||||||
|
cursor)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
return report_func(False, format_ex(exception), None)
|
self.disconnect()
|
||||||
|
return report_func(
|
||||||
|
False,
|
||||||
|
"Error connecting: %s" % format_ex(exception),
|
||||||
|
None)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
# When the verify query returned an error message, the
|
||||||
|
# verification failed.
|
||||||
|
if result is not None:
|
||||||
|
return report_func(
|
||||||
|
False,
|
||||||
|
"The pgbouncer is not connected to the expected " +
|
||||||
|
"PostgreSQL backend service: %s" % result,
|
||||||
|
cursor)
|
||||||
|
|
||||||
# When the verify query did not return an error message, then we
|
# When the verify query did not return an error message, then we
|
||||||
# are in the green.
|
# are in the green.
|
||||||
return report_func(True, None, cursor)
|
return report_func(True, None, cursor)
|
||||||
|
|
||||||
parent_conn, child_conn = multiprocessing.Pipe()
|
|
||||||
def report_func(true_or_false, exception, cursor):
|
def report_func(true_or_false, exception, cursor):
|
||||||
|
"""This function is used to report back results from the
|
||||||
|
subprocess that runs the check_func."""
|
||||||
|
# The cursor is included in the results for unit testing purposes.
|
||||||
|
# The cursor is modified, but since we're running in a subprocess,
|
||||||
|
# we wouldn't be able to see these modifications otherwise.
|
||||||
child_conn.send([true_or_false, exception, cursor])
|
child_conn.send([true_or_false, exception, cursor])
|
||||||
child_conn.close()
|
child_conn.close()
|
||||||
|
|
||||||
|
# Start the subprocess and wait for it to finish within the
|
||||||
|
# configured connect timeout. When the connect timeout is not
|
||||||
|
# met, the subprocess will be killed off.
|
||||||
|
parent_conn, child_conn = multiprocessing.Pipe()
|
||||||
proc = multiprocessing.Process(target=check_func, args=(report_func,))
|
proc = multiprocessing.Process(target=check_func, args=(report_func,))
|
||||||
proc.start()
|
proc.start()
|
||||||
proc.join(self.node_config.connect_timeout)
|
proc.join(self.node_config.connect_timeout)
|
||||||
|
|
|
@ -75,7 +75,6 @@ class StubCursor(list):
|
||||||
PostgreSQL server instance."""
|
PostgreSQL server instance."""
|
||||||
def __init__(self, *results):
|
def __init__(self, *results):
|
||||||
super().__init__(results)
|
super().__init__(results)
|
||||||
self.results = list(results)
|
|
||||||
self.statusmessage = None
|
self.statusmessage = None
|
||||||
self.rows = None
|
self.rows = None
|
||||||
self.query = None
|
self.query = None
|
||||||
|
@ -90,7 +89,7 @@ class StubCursor(list):
|
||||||
def execute(self, query, params=()):
|
def execute(self, query, params=()):
|
||||||
self.query = query
|
self.query = query
|
||||||
self.params = params
|
self.params = params
|
||||||
response = self.results.pop(0)
|
response = self.pop(0)
|
||||||
if isinstance(response, Exception):
|
if isinstance(response, Exception):
|
||||||
raise response
|
raise response
|
||||||
if callable(response):
|
if callable(response):
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""The tests in this module all make use of a fully stubbed psycopg2
|
||||||
|
connection to test the various PostgreSQL connection classes.
|
||||||
|
Using the stubs, all logic routes can be tested and we can spy on
|
||||||
|
the output of the various functions, without having to connect to
|
||||||
|
an actual PostgreSQL server."""
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import time
|
import time
|
||||||
|
@ -13,27 +20,27 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
def test_GivenAuthenticationFailure_PgConnection_RaisesException(self):
|
def test_GivenAuthenticationFailure_PgConnection_RaisesException(self):
|
||||||
self._test_connect_exception(
|
self._test_connect_exception(
|
||||||
PgConnection, StubPsycopg2().add_auth_failure(),
|
PgConnection, StubPsycopg2().add_auth_failure(),
|
||||||
PgConnectionFailed, "authentication failed")
|
PgConnectionFailed, 'authentication failed')
|
||||||
|
|
||||||
def test_GivenConnectionTimeout_PgConnection_RaisesException(self):
|
def test_GivenConnectionTimeout_PgConnection_RaisesException(self):
|
||||||
self._test_connect_exception(
|
self._test_connect_exception(
|
||||||
PgConnection, StubPsycopg2().add_conn_timeout(),
|
PgConnection, StubPsycopg2().add_conn_timeout(),
|
||||||
PgConnectionFailed, "timeout expired")
|
PgConnectionFailed, 'timeout expired')
|
||||||
|
|
||||||
def test_GivenConnectionFailure_PgConnection_RaisesException(self):
|
def test_GivenConnectionFailure_PgConnection_RaisesException(self):
|
||||||
self._test_connect_exception(
|
self._test_connect_exception(
|
||||||
PgConnection, StubPsycopg2().add_conn_failure(),
|
PgConnection, StubPsycopg2().add_conn_failure(),
|
||||||
PgConnectionFailed, "could not connect")
|
PgConnectionFailed, 'could not connect')
|
||||||
|
|
||||||
def test_GivenPostgresStartingUp_PgConnection_RaisesException(self):
|
def test_GivenPostgresStartingUp_PgConnection_RaisesException(self):
|
||||||
self._test_connect_exception(
|
self._test_connect_exception(
|
||||||
PgConnection, StubPsycopg2().add_admin_startup(),
|
PgConnection, StubPsycopg2().add_admin_startup(),
|
||||||
PgConnectionFailed, "system is starting up")
|
PgConnectionFailed, 'system is starting up')
|
||||||
|
|
||||||
def test_GivenPostgresShuttingDown_PgConnection_RaisesException(self):
|
def test_GivenPostgresShuttingDown_PgConnection_RaisesException(self):
|
||||||
self._test_connect_exception(
|
self._test_connect_exception(
|
||||||
PgConnection, StubPsycopg2().add_admin_shutdown(),
|
PgConnection, StubPsycopg2().add_admin_shutdown(),
|
||||||
PgConnectionFailed, "AdminShutdown")
|
PgConnectionFailed, 'AdminShutdown')
|
||||||
|
|
||||||
def _test_connect_exception(self, test_class, stub_psycopg2, err, msg):
|
def _test_connect_exception(self, test_class, stub_psycopg2, err, msg):
|
||||||
pg = test_class(NodeConfig(1), Logger(), stub_psycopg2)
|
pg = test_class(NodeConfig(1), Logger(), stub_psycopg2)
|
||||||
|
@ -43,13 +50,13 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_NodeConfig_IsAppliedToConnParams(self):
|
def test_NodeConfig_IsAppliedToConnParams(self):
|
||||||
node_config = NodeConfig(1)
|
node_config = NodeConfig(1)
|
||||||
node_config.host = "1.1.1.1"
|
node_config.host = '1.1.1.1'
|
||||||
node_config.port = 9999
|
node_config.port = 9999
|
||||||
pg = PgConnection(node_config, Logger(), None)
|
pg = PgConnection(node_config, Logger(), None)
|
||||||
|
|
||||||
self.assertEqual(1, pg.node_id)
|
self.assertEqual(1, pg.node_id)
|
||||||
self.assertEqual("1.1.1.1", pg.conn_params["host"])
|
self.assertEqual('1.1.1.1', pg.conn_params['host'])
|
||||||
self.assertEqual(9999, pg.conn_params["port"])
|
self.assertEqual(9999, pg.conn_params['port'])
|
||||||
|
|
||||||
def test_GivenNoneValueInNodeConfig_ValueIsOmittedInConnParams(self):
|
def test_GivenNoneValueInNodeConfig_ValueIsOmittedInConnParams(self):
|
||||||
node_config = NodeConfig(2)
|
node_config = NodeConfig(2)
|
||||||
|
@ -60,8 +67,8 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
pg.connect()
|
pg.connect()
|
||||||
|
|
||||||
self.assertEqual(2, pg.node_id)
|
self.assertEqual(2, pg.node_id)
|
||||||
self.assertNotIn("host", pg.conn_params)
|
self.assertNotIn('host', pg.conn_params)
|
||||||
self.assertNotIn("port", pg.conn_params)
|
self.assertNotIn('port', pg.conn_params)
|
||||||
|
|
||||||
def test_FirstConnect_SetsUpConnection(self):
|
def test_FirstConnect_SetsUpConnection(self):
|
||||||
stub_psycopg2 = StubPsycopg2().add_connection(StubConnection())
|
stub_psycopg2 = StubPsycopg2().add_connection(StubConnection())
|
||||||
|
@ -69,10 +76,10 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
|
|
||||||
result = pg.connect()
|
result = pg.connect()
|
||||||
|
|
||||||
self.assertEqual("CONN_CONNECTED", result)
|
self.assertEqual('CONN_CONNECTED', result)
|
||||||
|
|
||||||
def test_SecondConnect_PingsAndReusesConnection(self):
|
def test_SecondConnect_PingsAndReusesConnection(self):
|
||||||
cursor = StubCursor(("SELECT", [[1]]))
|
cursor = StubCursor(('SELECT', [[1]]))
|
||||||
conn = StubConnection(cursor)
|
conn = StubConnection(cursor)
|
||||||
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
||||||
pg = PgConnection(NodeConfig('b'), Logger(), stub_psycopg2)
|
pg = PgConnection(NodeConfig('b'), Logger(), stub_psycopg2)
|
||||||
|
@ -80,9 +87,9 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
result1 = pg.connect()
|
result1 = pg.connect()
|
||||||
self.assertIs(None, cursor.query)
|
self.assertIs(None, cursor.query)
|
||||||
result2 = pg.connect()
|
result2 = pg.connect()
|
||||||
self.assertEqual("SELECT 1", cursor.query)
|
self.assertEqual('SELECT 1', cursor.query)
|
||||||
self.assertEqual("CONN_CONNECTED", result1)
|
self.assertEqual('CONN_CONNECTED', result1)
|
||||||
self.assertEqual("CONN_REUSED", result2)
|
self.assertEqual('CONN_REUSED', result2)
|
||||||
|
|
||||||
def test_SecondConnectPingFails_SetsUpNewConnection(self):
|
def test_SecondConnectPingFails_SetsUpNewConnection(self):
|
||||||
conn1 = StubConnection(StubCursor(psycopg2.OperationalError()))
|
conn1 = StubConnection(StubCursor(psycopg2.OperationalError()))
|
||||||
|
@ -93,8 +100,8 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
result1 = pg.connect() # Connection OK
|
result1 = pg.connect() # Connection OK
|
||||||
result2 = pg.connect() # Ping fails, reconnectt
|
result2 = pg.connect() # Ping fails, reconnectt
|
||||||
|
|
||||||
self.assertEqual("CONN_CONNECTED", result1)
|
self.assertEqual('CONN_CONNECTED', result1)
|
||||||
self.assertEqual("CONN_RECONNECTED", result2)
|
self.assertEqual('CONN_RECONNECTED', result2)
|
||||||
|
|
||||||
def test_Disconnect_ClosesConnection(self):
|
def test_Disconnect_ClosesConnection(self):
|
||||||
conn = StubConnection()
|
conn = StubConnection()
|
||||||
|
@ -110,10 +117,10 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
conn1 = StubConnection(
|
conn1 = StubConnection(
|
||||||
StubCursor(psycopg2.OperationalError()))
|
StubCursor(psycopg2.OperationalError()))
|
||||||
conn2 = StubConnection(
|
conn2 = StubConnection(
|
||||||
StubCursor(("SELECT", [])),
|
StubCursor(('SELECT', [])),
|
||||||
StubCursor(("SELECT", [])))
|
StubCursor(('SELECT', [])))
|
||||||
conn3 = StubConnection(
|
conn3 = StubConnection(
|
||||||
StubCursor(("SELECT", [])))
|
StubCursor(('SELECT', [])))
|
||||||
|
|
||||||
stub_psycopg2 = StubPsycopg2(conn1, conn2, conn3)
|
stub_psycopg2 = StubPsycopg2(conn1, conn2, conn3)
|
||||||
pg = PgConnection(NodeConfig('b'), Logger(), stub_psycopg2)
|
pg = PgConnection(NodeConfig('b'), Logger(), stub_psycopg2)
|
||||||
|
@ -126,12 +133,12 @@ class PgConnectionTests(unittest.TestCase):
|
||||||
result5 = pg.connect() # Connection 3 OK
|
result5 = pg.connect() # Connection 3 OK
|
||||||
result6 = pg.connect() # Ping success
|
result6 = pg.connect() # Ping success
|
||||||
|
|
||||||
self.assertEqual("CONN_CONNECTED", result1)
|
self.assertEqual('CONN_CONNECTED', result1)
|
||||||
self.assertEqual("CONN_RECONNECTED", result2)
|
self.assertEqual('CONN_RECONNECTED', result2)
|
||||||
self.assertEqual("CONN_REUSED", result3)
|
self.assertEqual('CONN_REUSED', result3)
|
||||||
self.assertEqual("CONN_REUSED", result4)
|
self.assertEqual('CONN_REUSED', result4)
|
||||||
self.assertEqual("CONN_CONNECTED", result5)
|
self.assertEqual('CONN_CONNECTED', result5)
|
||||||
self.assertEqual("CONN_REUSED", result6)
|
self.assertEqual('CONN_REUSED', result6)
|
||||||
|
|
||||||
|
|
||||||
class PgReplicationConnectionTests(unittest.TestCase):
|
class PgReplicationConnectionTests(unittest.TestCase):
|
||||||
|
@ -139,157 +146,185 @@ class PgReplicationConnectionTests(unittest.TestCase):
|
||||||
conn = StubConnection()
|
conn = StubConnection()
|
||||||
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), stub_psycopg2)
|
NodeConfig('foo'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
pg.connect()
|
pg.connect()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
psycopg2.extras.LogicalReplicationConnection,
|
psycopg2.extras.LogicalReplicationConnection,
|
||||||
pg.conn_params["connection_factory"])
|
pg.conn_params['connection_factory'])
|
||||||
|
|
||||||
def test_GivenFailingConnection_ReplicationStatusIsOffline(self):
|
def test_GivenFailingConnection_ReplicationStatusIsOffline(self):
|
||||||
stub_psycopg2 = StubPsycopg2().add_auth_failure()
|
stub_psycopg2 = StubPsycopg2().add_auth_failure()
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), stub_psycopg2)
|
NodeConfig('foo'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
status = pg.get_replication_status()
|
status = pg.get_replication_status()
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
"status": "NODE_OFFLINE",
|
'status': 'NODE_OFFLINE',
|
||||||
"system_id": None,
|
'system_id': None,
|
||||||
"timeline_id": None}, status)
|
'timeline_id': None}, status)
|
||||||
|
|
||||||
def test_GivenFailingStandbyQuery_ReplicationStatusRaisesException(self):
|
def test_GivenFailingStandbyQuery_ReplicationStatusRaisesException(self):
|
||||||
conn = StubConnection(StubCursor(psycopg2.OperationalError()))
|
conn = StubConnection(StubCursor(psycopg2.OperationalError()))
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), StubPsycopg2(conn))
|
NodeConfig('foo'), Logger(), StubPsycopg2(conn))
|
||||||
|
|
||||||
with self.assertRaises(RetrievingPgReplicationStatusFailed) as context:
|
with self.assertRaises(RetrievingPgReplicationStatusFailed) as context:
|
||||||
pg.get_replication_status()
|
pg.get_replication_status()
|
||||||
self.assertIn("pg_is_in_recovery() failed", str(context.exception))
|
self.assertIn('pg_is_in_recovery() failed', str(context.exception))
|
||||||
|
|
||||||
def test_GivenFailingIdentifySystemQuery_ReplicationStatusRaisesException(self):
|
def test_GivenFailingIdentifySystemQuery_ReplicationStatusRaisesException(self):
|
||||||
conn = StubConnection(
|
conn = StubConnection(
|
||||||
StubCursor(("SELECT", [[False]])),
|
StubCursor(('SELECT', [[False]])),
|
||||||
psycopg2.OperationalError())
|
psycopg2.OperationalError())
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), StubPsycopg2(conn))
|
NodeConfig('foo'), Logger(), StubPsycopg2(conn))
|
||||||
|
|
||||||
with self.assertRaises(RetrievingPgReplicationStatusFailed) as context:
|
with self.assertRaises(RetrievingPgReplicationStatusFailed) as context:
|
||||||
pg.get_replication_status()
|
pg.get_replication_status()
|
||||||
self.assertIn("IDENTIFY_SYSTEM failed", str(context.exception))
|
self.assertIn('IDENTIFY_SYSTEM failed', str(context.exception))
|
||||||
|
|
||||||
def test_GivenConnectionToPrimaryNode_ReplicationStatusIsPrimary(self):
|
def test_GivenConnectionToPrimaryNode_ReplicationStatusIsPrimary(self):
|
||||||
conn = StubConnection(
|
conn = StubConnection(
|
||||||
StubCursor(("SELECT", [[False]])),
|
StubCursor(('SELECT', [[False]])),
|
||||||
StubCursor(("IDENTIFY_SYSTEM", [["id", 1234, "other", "fields"]])))
|
StubCursor(('IDENTIFY_SYSTEM', [['id', 1234, 'other', 'fields']])))
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), stub_psycopg2)
|
NodeConfig('foo'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
status = pg.get_replication_status()
|
status = pg.get_replication_status()
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
"status": "NODE_PRIMARY",
|
'status': 'NODE_PRIMARY',
|
||||||
"system_id": "id",
|
'system_id': 'id',
|
||||||
"timeline_id": 1234}, status)
|
'timeline_id': 1234}, status)
|
||||||
|
|
||||||
def test_GivenConnectionToStandbyNode_ReplicationStatusIsStandby(self):
|
def test_GivenConnectionToStandbyNode_ReplicationStatusIsStandby(self):
|
||||||
conn = StubConnection(
|
conn = StubConnection(
|
||||||
StubCursor(("SELECT", [[True]])),
|
StubCursor(('SELECT', [[True]])),
|
||||||
StubCursor(("IDENTIFY_SYSTEM", [["towel", 42, "other", "fields"]])))
|
StubCursor(('IDENTIFY_SYSTEM', [['towel', 42, 'other', 'fields']])))
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
pg = PgReplicationConnection(
|
pg = PgReplicationConnection(
|
||||||
NodeConfig("foo"), Logger(), stub_psycopg2)
|
NodeConfig('foo'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
status = pg.get_replication_status()
|
status = pg.get_replication_status()
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
"status": "NODE_STANDBY",
|
'status': 'NODE_STANDBY',
|
||||||
"system_id": "towel",
|
'system_id': 'towel',
|
||||||
"timeline_id": 42}, status)
|
'timeline_id': 42}, status)
|
||||||
|
|
||||||
|
|
||||||
class PgConnectionViaPgbouncerTests(unittest.TestCase):
|
class PgConnectionViaPgbouncerTests(unittest.TestCase):
|
||||||
def test_NodeConfigAndPgBouncerConfig_AreMergedInConnParams(self):
|
def test_NodeConfigAndPgBouncerConfig_AreMergedInConnParams(self):
|
||||||
node_config = NodeConfig(777)
|
node_config = NodeConfig(777)
|
||||||
node_config.host = "1.1.1.1"
|
node_config.host = '1.1.1.1'
|
||||||
node_config.port = 9999
|
node_config.port = 9999
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
|
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), None)
|
node_config, pgbouncer_config, Logger(), None)
|
||||||
|
|
||||||
self.assertEqual(777, pg.node_id)
|
self.assertEqual(777, pg.node_id)
|
||||||
self.assertEqual("template1", pg.conn_params["database"])
|
self.assertEqual('template1', pg.conn_params['database'])
|
||||||
self.assertEqual("192.168.0.1", pg.conn_params["host"])
|
self.assertEqual('192.168.0.1', pg.conn_params['host'])
|
||||||
self.assertEqual(7654, pg.conn_params["port"])
|
self.assertEqual(7654, pg.conn_params['port'])
|
||||||
|
|
||||||
def test_WhenVerifyQueryCannotConnect_ExceptionIsRaised(self):
|
def test_WhenVerifyQueryCannotConnect_ErrorIsReported(self):
|
||||||
stub_psycopg2 = StubPsycopg2().add_auth_failure()
|
stub_psycopg2 = StubPsycopg2().add_auth_failure()
|
||||||
node_config = NodeConfig('xyz123')
|
node_config = NodeConfig('xyz123')
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
ok, err, cursor = pg.verify_connection()
|
ok, err, cursor = pg.verify_connection()
|
||||||
self.assertFalse(ok)
|
self.assertFalse(ok)
|
||||||
self.assertIs(None, cursor)
|
self.assertIs(None, cursor)
|
||||||
self.assertIn("password authentication failed", str(err))
|
self.assertIn('password authentication failed', str(err))
|
||||||
|
|
||||||
def test_WhenVerifyQueryTimesOut_ExceptionIsRaised(self):
|
def test_WhenVerifyQueryCannotCreateCursor_ErrorIsReported(self):
|
||||||
|
conn = StubConnection(psycopg2.OperationalError('borked cursor'))
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
node_config = NodeConfig('xyz123')
|
||||||
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
|
pg = PgConnectionViaPgbouncer(
|
||||||
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
ok, err, cursor = pg.verify_connection()
|
||||||
|
self.assertFalse(ok)
|
||||||
|
self.assertIs(None, cursor)
|
||||||
|
self.assertIn('Error connecting', str(err))
|
||||||
|
self.assertIn('borked cursor', str(err))
|
||||||
|
|
||||||
|
def test_WhenVerifyQueryFails_ErrorIsReported(self):
|
||||||
|
cursor = StubCursor(psycopg2.OperationalError('borked query'))
|
||||||
|
conn = StubConnection(cursor)
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
node_config = NodeConfig('xyz123')
|
||||||
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
|
pg = PgConnectionViaPgbouncer(
|
||||||
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
ok, err, cursor = pg.verify_connection()
|
||||||
|
self.assertFalse(ok)
|
||||||
|
self.assertIn('Error running query', str(err))
|
||||||
|
self.assertIn('borked query', str(err))
|
||||||
|
|
||||||
|
def test_WhenVerifyQueryTimesOut_ErrorIsReported(self):
|
||||||
def sleeping_query(query):
|
def sleeping_query(query):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
conn = StubConnection(StubCursor(sleeping_query))
|
conn = StubConnection(StubCursor(sleeping_query))
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
node_config = NodeConfig('xyz123')
|
node_config = NodeConfig('xyz123')
|
||||||
node_config.connect_timeout = 0.01
|
node_config.connect_timeout = 0.01
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
ok, err, cursor = pg.verify_connection()
|
ok, err, cursor = pg.verify_connection()
|
||||||
self.assertFalse(ok)
|
self.assertFalse(ok)
|
||||||
self.assertIs(None, cursor)
|
self.assertIs(None, cursor)
|
||||||
self.assertIn("Connection attempt timed out", str(err))
|
self.assertIn('Connection attempt timed out', str(err))
|
||||||
|
|
||||||
def test_WhenVerifyQueryRuns_NodeHostAndPortAreQueried(self):
|
def test_WhenVerifyQueryRuns_NodeHostAndPortAreQueried(self):
|
||||||
conn = StubConnection(StubCursor(('SELECT', [[None]])))
|
conn = StubConnection(StubCursor(('SELECT', [[None]])))
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
node_config = NodeConfig('uh')
|
node_config = NodeConfig('uh')
|
||||||
node_config.host = "111.222.111.222"
|
node_config.host = '111.222.111.222'
|
||||||
node_config.port = 1122
|
node_config.port = 1122
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
ok, err, cursor = pg.verify_connection()
|
ok, err, cursor = pg.verify_connection()
|
||||||
self.assertIn("inet_server_addr()", cursor.query)
|
self.assertIn('inet_server_addr()', cursor.query)
|
||||||
self.assertIn("%(host)s", cursor.query)
|
self.assertIn('%(host)s', cursor.query)
|
||||||
self.assertIn("inet_server_port()", cursor.query)
|
self.assertIn('inet_server_port()', cursor.query)
|
||||||
self.assertIn("%(port)s", cursor.query)
|
self.assertIn('%(port)s', cursor.query)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
"host": "111.222.111.222",
|
'host': '111.222.111.222',
|
||||||
"port": 1122
|
'port': 1122
|
||||||
}, cursor.params)
|
}, cursor.params)
|
||||||
|
|
||||||
def test_WhenVerifyQueryReturnsIssue_IssueIsReported(self):
|
def test_WhenVerifyQueryReturnsIssue_ErrorIsReported(self):
|
||||||
cursor = StubCursor(('SELECT', [['Bokito has escaped']]))
|
cursor = StubCursor(('SELECT', [['Bokito has escaped']]))
|
||||||
conn = StubConnection(cursor)
|
conn = StubConnection(cursor)
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
node_config = NodeConfig('xyz123')
|
node_config = NodeConfig('xyz123')
|
||||||
node_config.host = "111.222.111.222"
|
node_config.host = '111.222.111.222'
|
||||||
node_config.port = 1122
|
node_config.port = 1122
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
ok, err, _ = pg.verify_connection()
|
ok, err, _ = pg.verify_connection()
|
||||||
self.assertFalse(ok)
|
self.assertFalse(ok)
|
||||||
self.assertIn("not connected to the expected PostgreSQL backend", err)
|
self.assertIn('not connected to the expected PostgreSQL backend', err)
|
||||||
self.assertIn("Bokito has escaped", err)
|
self.assertIn('Bokito has escaped', err)
|
||||||
|
|
||||||
def test_WhenVerifyQueryReturnsNoIssue_OkIsReported(self):
|
def test_WhenVerifyQueryReturnsNoIssue_OkIsReported(self):
|
||||||
conn = StubConnection(StubCursor(('SELECT', [[None]])))
|
conn = StubConnection(StubCursor(('SELECT', [[None]])))
|
||||||
stub_psycopg2 = StubPsycopg2(conn)
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
node_config = NodeConfig('xyz123')
|
node_config = NodeConfig('xyz123')
|
||||||
pgbouncer_config = {"host": "192.168.0.1", "port": 7654}
|
pgbouncer_config = {'host': '192.168.0.1', 'port': 7654}
|
||||||
pg = PgConnectionViaPgbouncer(
|
pg = PgConnectionViaPgbouncer(
|
||||||
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
node_config, pgbouncer_config, Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
@ -297,13 +332,69 @@ class PgConnectionViaPgbouncerTests(unittest.TestCase):
|
||||||
self.assertTrue(ok)
|
self.assertTrue(ok)
|
||||||
self.assertIs(None, err)
|
self.assertIs(None, err)
|
||||||
|
|
||||||
|
|
||||||
class PgBouncerConsoleConnectionTests(unittest.TestCase):
|
class PgBouncerConsoleConnectionTests(unittest.TestCase):
|
||||||
def test_OnConnect_AutoCommitIsEnabled(self):
|
def test_OnConnect_DatabaseIsPgbouncerAndAutoCommitIsEnabled(self):
|
||||||
conn = StubConnection()
|
conn = StubConnection()
|
||||||
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
stub_psycopg2 = StubPsycopg2().add_connection(conn)
|
||||||
pg = PgBouncerConsoleConnection(
|
pg = PgBouncerConsoleConnection(
|
||||||
NodeConfig("bob"), Logger(), stub_psycopg2)
|
NodeConfig('bob'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
# Autocommit must be enabled, because the console does
|
||||||
|
# not support transactions.
|
||||||
self.assertFalse(conn.autocommit)
|
self.assertFalse(conn.autocommit)
|
||||||
pg.connect()
|
pg.connect()
|
||||||
self.assertTrue(conn.autocommit)
|
self.assertTrue(conn.autocommit)
|
||||||
|
|
||||||
|
# To connect to the console, the database 'pgbouncer' must be used.
|
||||||
|
self.assertEqual('pgbouncer', pg.conn_params['database'])
|
||||||
|
|
||||||
|
def test_OnReconnect_ShowVersionIsUsedForPinging(self):
|
||||||
|
'''Because the pgbouncer console does not support SELECT queries,
|
||||||
|
we have to make use of another query to check if the console
|
||||||
|
connection is still alive.'''
|
||||||
|
cursor = StubCursor(('SHOW', [['the version']]))
|
||||||
|
conn = StubConnection(cursor)
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
pg = PgBouncerConsoleConnection(
|
||||||
|
NodeConfig('bar'), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
pg.connect()
|
||||||
|
result = pg.connect()
|
||||||
|
self.assertEqual('CONN_REUSED', result)
|
||||||
|
self.assertEqual('SHOW VERSION', cursor.query)
|
||||||
|
|
||||||
|
def test_WhenReloadFails_ExceptionIsRaised(self):
|
||||||
|
conn = StubConnection(psycopg2.OperationalError('borked reload'))
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
pg = PgBouncerConsoleConnection(
|
||||||
|
NodeConfig(1001), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
with self.assertRaises(ReloadingPgbouncerFailed) as context:
|
||||||
|
pg.reload()
|
||||||
|
self.assertIn('borked reload', str(context.exception))
|
||||||
|
|
||||||
|
def test_WhenReloadReturnsAnUnexpectedStatusMessage_ExceptionIsRaised(self):
|
||||||
|
cursor = StubCursor(('WEIRD', [[None]]))
|
||||||
|
conn = StubConnection(cursor)
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
pg = PgBouncerConsoleConnection(
|
||||||
|
NodeConfig(1002), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
with self.assertRaises(ReloadingPgbouncerFailed) as context:
|
||||||
|
pg.reload()
|
||||||
|
self.assertIn(
|
||||||
|
'Unexpected status message: WEIRD',
|
||||||
|
str(context.exception))
|
||||||
|
|
||||||
|
def test_WhenReloadIsSuccessful_NoExceptionIsRaised(self):
|
||||||
|
cursor = StubCursor(('RELOAD', [[None]]))
|
||||||
|
conn = StubConnection(cursor)
|
||||||
|
stub_psycopg2 = StubPsycopg2(conn)
|
||||||
|
pg = PgBouncerConsoleConnection(
|
||||||
|
NodeConfig(1002), Logger(), stub_psycopg2)
|
||||||
|
|
||||||
|
pg.reload()
|
||||||
|
self.assertEqual('RELOAD', cursor.query)
|
||||||
|
self.assertEqual('RELOAD', cursor.statusmessage)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue