pgbouncemgr/tests/test_state.py

465 lines
15 KiB
Python

# -*- coding: utf-8 -*-
import unittest
from pgbouncemgr.state import *
class StateTests(unittest.TestCase):
def test_GivenFreshState_DefaultsAreSetCorrectly(self):
state = State()
self.assertIsNone(state.system_id)
self.assertIsNone(state.timeline_id)
self.assertIsNone(state.pgbouncer_config)
self.assertIsNone(state.leader_node_id)
self.assertIsNone(state.leader_error)
self.assertEqual(LEADER_UNKNOWN, state.leader_status)
self.assertEqual({}, state.nodes)
self.assertFalse(state.modified)
def test_GivenFreshState_ModifiedFlagIsNotSet(self):
state = State()
self.assertFalse(state.modified)
def test_GivenFreshState_IsCleanSlate_ReturnsTrue(self):
state = State()
self.assertTrue(state.is_clean_slate())
def test_GivenStateWithSystemIdAssigned_IsCleanSlate_ReturnsFalse(self):
state = State()
state.system_id = "whatever"
self.assertFalse(state.is_clean_slate())
def test_GivenFreshState_SetSystemId_SetsSystemId(self):
state = State()
state.system_id = "booya"
self.assertEqual("booya", state.system_id)
def test_GivenStateWithSystemId_SetSameSystemId_IsOk(self):
state = State()
state.system_id = "booya"
state.modified = False
state.system_id = "booya"
self.assertFalse(state.modified)
def test_GivenStateWithSystemId_SetDifferentSystemId_RaisesException(self):
state = State()
state.system_id = "booya"
state.modified = False
with self.assertRaises(SystemIdChanged) as context:
state.system_id = "baayo"
self.assertIn("from=booya, to=baayo", str(context.exception))
def test_GivenFreshState_SetTimelineId_SetsTimelineId(self):
state = State()
state.timeline_id = 666
self.assertEqual(666, state.timeline_id)
self.assertTrue(state.modified)
def test_WithNonIntegerInput_SetTimelineId_RaisesException(self):
state = State()
with self.assertRaises(InvalidTimelineId) as context:
state.timeline_id = "one hundred"
self.assertIn("invalid value=one hundred", str(context.exception))
def test_GivenStateWithTimelineId_SetLowerTimelineId_RaisesException(self):
state = State()
state.timeline_id = "50"
with self.assertRaises(TimelineIdLowered) as context:
state.timeline_id = 49
self.assertIn("(from=50, to=49)", str(context.exception))
def test_GivenStateWithTimelineId_SetSameTimelineId_IsOk(self):
state = State()
state.timeline_id = 50
state.modified = False
state.timeline_id = "50"
self.assertFalse(state.modified)
def test_GivenStateWithTimelineId_SetHigherTimelineId_IsOk(self):
state = State()
state.timeline_id = 50
state.modified = False
state.timeline_id = "51"
self.assertEqual(51, state.timeline_id)
self.assertTrue(state.modified)
def test_SetLeaderStatus_SetsLeaderStatus(self):
state = State()
state.modified = False
state.leader_status = LEADER_CONNECTED
self.assertEqual(LEADER_CONNECTED, state.leader_status)
self.assertTrue(state.modified)
def test_SetLeaderStatus_ToNonExistentStatus_RaisesException(self):
state = State()
state.modified = False
with self.assertRaises(InvalidLeaderStatus) as context:
state.leader_status = "gosh"
self.assertIn("'gosh'", str(context.exception))
def test_SetLeaderError_SetsLeaderError(self):
state = State()
state.modified = False
state.leader_error = "God disappeared in a puff of logic"
self.assertEqual("God disappeared in a puff of logic", state.leader_error)
self.assertTrue(state.modified)
state.modified = False
state.leader_error = None
self.assertEqual(None, state.leader_error)
self.assertTrue(state.modified)
class NodeCollectionTests(unittest.TestCase):
def test_WithNodeNotYetInState_AddNode_AddsNodeState(self):
state = State()
node = state.add_node(1)
self.assertEqual(1, node.node_id)
self.assertTrue(state.modified)
def test_WithNodeAlreadyInState_AddNode_RaisesException(self):
state = State()
state.add_node(123)
with self.assertRaises(DuplicateNodeAdded) as context:
state.add_node(123)
self.assertIn("duplicate node_id=123", str(context.exception))
def test_WithNodeNotInState_getNode_RaisesException(self):
state = State()
with self.assertRaises(UnknownNodeRequested):
state.get_node("that does not exist")
def test_WithNodeInState_getNode_ReturnsNode(self):
state = State()
node = state.add_node("that does exist")
node_from_state = state.get_node("that does exist")
self.assertEqual(node, node_from_state)
def test_WithUnknownNode_SetLeaderNode_RaisesException(self):
state1 = State()
state2 = State()
node = state2.add_node(1337)
with self.assertRaises(UnknownNodeRequested) as context:
state1.promote_node(node)
self.assertIn("unknown node_id=1337", str(context.exception))
def test_WithNodeWithoutSystemId_SetLeaderNode_RaisesException(self):
state = State()
node = state.add_node(1)
node.timeline_id = 1
node.config.pgbouncer_config = "/some/path/to/config"
with self.assertRaises(NodeCannotBePromoted) as context:
node.promote()
self.assertIn("Node '1'", str(context.exception))
self.assertIn("node has no system_id", str(context.exception))
def test_WithNodeWithoutTimelineId_SetLeaderNode_RaisesException(self):
state = State()
node = state.add_node(1)
node.system_id = "system"
node.config.pgbouncer_config = "/some/path/to/config"
with self.assertRaises(NodeCannotBePromoted) as context:
node.promote()
self.assertIn("Node '1'", str(context.exception))
self.assertIn("node has no timeline_id", str(context.exception))
def test_WithNodeWithoutPgbouncerConfig_SetLeaderNode_RaisesException(self):
state = State()
node = state.add_node(1)
node.system_id = "a7d8a9347df789saghdfs"
node.timeline_id = 11111111111
with self.assertRaises(NodeCannotBePromoted) as context:
node.promote()
self.assertIn("Node '1'", str(context.exception))
self.assertIn("node has no pgbouncer_config", str(context.exception))
def test_SetLeaderNode_SetsLeaderNode_WithUnknownLeaderStatus(self):
state = State()
node = state.add_node(1)
node.config.pgbouncer_config = "/some/path/to/config"
node.system_id = "SuperCluster"
node.timeline_id = " 005 "
node.promote()
self.assertEqual("SuperCluster", state.system_id)
self.assertEqual(5, state.timeline_id)
self.assertEqual(1, state.leader_node_id)
self.assertEqual(LEADER_UNKNOWN, state.leader_status)
self.assertEqual(None, state.leader_error)
self.assertTrue(state.modified)
def test_SetLeaderNode_ToSameLeader_ResetsLeaderNode_WithUnknownLeaderStatus(self):
state = State()
node = state.add_node(1)
node.config.pgbouncer_config = "/some/path/to/config"
node.system_id = "1.2.3.4.5.6.7.8.9.10.11"
node.timeline_id = 12
node.promote()
state.leader_status = LEADER_CONNECTED
state.leader_error = "Just testin'!"
state.modified = False
node.promote()
self.assertEqual(None, state.leader_error)
self.assertEqual(LEADER_UNKNOWN, state.leader_status)
self.assertTrue(state.modified)
def test_SetLeaderNode_ToNodeWithDifferentSystemId_RaisesException(self):
state = State()
node1 = state.add_node(1)
node1.config.pgbouncer_config = "/some/path/to/config1"
node1.system_id = "systemA"
node1.timeline_id = 10
node2 = state.add_node(2)
node2.config.pgbouncer_config = "/some/path/to/config2"
node2.system_id = "systemB"
node2.timeline_id = 10
node1.promote()
with self.assertRaises(SystemIdChanged):
node2.promote()
def test_SetLeaderNode_ToNodeWithLowerTimelineId_RaisesException(self):
state = State()
node1 = state.add_node(1)
node1.config.pgbouncer_config = "/some/path/to/config1"
node1.system_id = "systemX"
node1.timeline_id = 10
node2 = state.add_node(2)
node2.config.pgbouncer_config = "/some/path/to/config2"
node2.system_id = "systemX"
node2.timeline_id = 9
node1.promote()
with self.assertRaises(TimelineIdLowered):
node2.promote()
def test_SetLeaderNode_ToNodeWithSameOrHigherTimelineId_IsOk(self):
state = State()
node1 = state.add_node(1)
node1.config.pgbouncer_config = "/some/path/to/config1"
node1.system_id = "systemX"
node1.timeline_id = 42
node2 = state.add_node(2)
node2.config.pgbouncer_config = "/some/path/to/config2"
node2.system_id = "systemX"
node2.timeline_id = 42
node3 = state.add_node(3)
node3.config.pgbouncer_config = "/some/path/to/config3"
node3.system_id = "systemX"
node3.timeline_id = 43
state.modified = False
node1.promote()
self.assertEqual(1, state.leader_node_id)
self.assertEqual(42, state.timeline_id)
self.assertTrue(state.modified)
state.modified = False
node2.promote()
self.assertEqual(2, state.leader_node_id)
self.assertEqual(42, state.timeline_id)
self.assertTrue(state.modified)
state.modified = False
node3.promote()
self.assertEqual(3, state.leader_node_id)
self.assertEqual(43, state.timeline_id)
self.assertTrue(state.modified)
class NodeTests(unittest.TestCase):
def test_WithNoneValue_SetSystemId_RaisesException(self):
state = State()
node = state.add_node("break me")
state.modified = False
with self.assertRaises(InvalidSystemId) as context:
node.system_id = None
self.assertIn("invalid system_id=None", str(context.exception))
self.assertFalse(state.modified)
def test_WithEmptyString_SetSystemId_RaisesException(self):
state = State()
node = state.add_node("break me")
state.modified = False
with self.assertRaises(InvalidSystemId) as context:
node.system_id = " \t\r\n\t "
self.assertIn("invalid system_id=' \t\r\n\t '", str(context.exception))
self.assertFalse(state.modified)
def test_SetSystemId_SetsSystemId_AndNotifiesChangeToState(self):
state = State()
node = state.add_node(1)
state.modified = False
node.system_id = "X"
self.assertEqual("X", node.system_id)
self.assertTrue(state.modified)
def test_WithNonIntegerInput_SetTimelineId_RaisesException(self):
state = State()
node = state.add_node("break me")
with self.assertRaises(InvalidTimelineId) as context:
node.timeline_id = "TARDIS"
self.assertIn("invalid value=TARDIS", str(context.exception))
def test_SetTimelineId_SetsTimelineId_AndNotifiesChangeToState(self):
state = State()
node = state.add_node(1)
state.modified = False
node.timeline_id = 25
self.assertEqual(25, node.timeline_id)
self.assertTrue(state.modified)
def test_SetStatus_SetsStatus_AndNotifiesChangeToState(self):
state = State()
node = state.add_node(1)
state.modified = False
node.set_status(NODE_PRIMARY)
self.assertEqual(NODE_PRIMARY, node.status)
self.assertTrue(state.modified)
def test_WithInvalidStatus_SetStatus_RaisesException(self):
state = State()
node = state.add_node(1)
state.modified = False
with self.assertRaises(InvalidNodeStatus) as context:
node.set_status("DERAILED")
self.assertIn("'DERAILED'", str(context.exception))
self.assertFalse(state.modified)
def test_SetError_ToString_SetsError_AndNotifiesChangeToState(self):
state = State()
node = state.add_node(1)
state.modified = False
node.set_error("Found some spare bits at IKEA.py line 141")
self.assertEqual("Found some spare bits at IKEA.py line 141", node.err)
self.assertTrue(state.modified)
def test_SetError_ToNone_ClearsError_AndNotifiesChangeToState(self):
state = State()
node = state.add_node(1)
node.set_error("Mouse lost its ball")
state.modified = False
node.set_error(None)
self.assertEqual(None, node.err)
self.assertTrue(state.modified)
def test_WhenNothingChanges_NoChangeIsNotifiedToState(self):
state = State()
node = state.add_node("x")
node.system_id = "aaaaaaa"
node.timeline_id = 55
node.set_status(NODE_PRIMARY)
node.set_error("Just testin'")
state.modified = False
node.system_id = "aaaaaaa"
node.timeline_id = 55
node.set_status(NODE_PRIMARY)
node.set_error("Just testin'")
self.assertFalse(state.modified)
class StateExportTests(unittest.TestCase):
def test_GivenFilledState_Export_ExportsDataForStore(self):
self.maxDiff = 5000;
state = State()
node1 = state.add_node(1)
node1.config.pgbouncer_config = "/some/path/to/config1"
node1.system_id = "System X"
node1.timeline_id = 555
node1.set_status(NODE_PRIMARY)
node1.set_error("Some error for 1")
node2 = state.add_node(2)
node2.config.pgbouncer_config = "/some/path/to/config2"
node2.system_id = "System Y"
node2.timeline_id = 1
node2.set_status(NODE_STANDBY)
node2.set_error("Some error for 2")
node1.promote()
state.leader_status = LEADER_CONNECTED
state.leader_error = "Some error for leader connection"
export = state.export()
self.assertEqual({
"system_id": "System X",
"timeline_id": 555,
"pgbouncer_config": "/some/path/to/config1",
"leader_node_id": 1,
"leader_status": LEADER_CONNECTED,
"leader_error": "Some error for leader connection",
"nodes": {
1: {
"node_id": 1,
"config": {
"pgbouncer_config": "/some/path/to/config1",
"host": None,
"port": None
},
"system_id": "System X",
"timeline_id": 555,
"is_leader": True,
"status": NODE_PRIMARY,
"error": "Some error for 1"
},
2: {
"node_id": 2,
"config": {
"pgbouncer_config": "/some/path/to/config2",
"host": None,
"port": None
},
"system_id": "System Y",
"timeline_id": 1,
"is_leader": False,
"status": NODE_STANDBY,
"error": "Some error for 2"
}
}
}, export)