Work in progress on state store.

This commit is contained in:
Maurice Makaay 2019-12-05 15:38:49 +01:00
parent 6f00ca34cd
commit e5dbf96cb4
11 changed files with 315 additions and 122 deletions

View File

@ -3,42 +3,62 @@
PostgreSQL cluster and that reconfigures pgbouncer when needed."""
from argparse import ArgumentParser
from pgbouncemgr.logger import Logger
from pgbouncemgr.logger import Logger, ConsoleLogTarget, SyslogLogTarget
from pgbouncemgr.config import Config
from pgbouncemgr.state import State
from pgbouncemgr.state_store import StateStore
DEFAULT_CONFIG = "/etc/pgbouncer/pgbouncemgr.yaml"
DEFAULT_LOG_FACILITY = "LOG_LOCAL1"
def main():
"""Starts the pgbouncemgr main application."""
args = _parse_arguments()
config = Config(args.config)
log = Logger("pgbouncemgr", args.log_facility, args.debug, args.verbose)
class Manager():
def __init__(self, argv=None):
args = _parse_arguments(argv)
self.config = Config(args.config)
self._create_logger(args)
self._create_state()
def _create_logger(self, args):
self.log = Logger()
self.log.append(ConsoleLogTarget(args.verbose, args.debug))
if args.log_facility.lower() != 'none':
self.log.append(SyslogLogTarget("pgbouncemgr", args.log_facility))
def _create_state(self):
self.state = State.fromConfig(self.config, self.log)
self.state_store = StateStore(self.config.state_file, self.state)
self.state_store.load()
def start(self):
self.log.info("Not yet!")
self.log.debug("Work in progres...")
self.log.warning("Beware!")
self.log.error("I will crash now")
def _parse_arguments():
def _parse_arguments(args):
parser = ArgumentParser(description="pgbouncemgr")
parser.add_argument(
"-v", "--verbose",
default=False, action="store_true",
help="enable verbose output (default: disabled)")
help="enable verbose console output (default: disabled)")
parser.add_argument(
"-d", "--debug",
default=False, action="store_true",
help="enable debugging output (default: disabled)")
help="enable debugging console output (default: disabled)")
parser.add_argument(
"-f", "--log-facility",
default=DEFAULT_LOG_FACILITY,
help="syslog facility to use (default: %s)" % DEFAULT_LOG_FACILITY)
help="syslog facility to use or 'none' to disable syslog logging " +
"(default: %s)" % DEFAULT_LOG_FACILITY)
parser.add_argument(
"--config",
default=DEFAULT_CONFIG,
help="config file (default: %s)" % DEFAULT_CONFIG)
args = parser.parse_args()
return args
return parser.parse_args(args)
if __name__ == "__main__":
main()
Manager(None).start()

View File

@ -7,6 +7,10 @@ class NodeConfig():
self._pgbouncer_config = None
self.host = None
self.port = None
self.connect_timeout = 1
self.name = 'template1'
self.user = 'pgbouncemgr'
self.password = None
@property
def pgbouncer_config(self):
@ -21,6 +25,8 @@ class NodeConfig():
self._pgbouncer_config = path
def export(self):
"""Exports the data for the node configuration, that we want
to end up in the state data."""
return {
"pgbouncer_config": self.pgbouncer_config,
"host": self.host,

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# no-pylint: disable=missing-docstring,broad-except,too-many-instance-attributes
import os
from pgbouncemgr.node_config import NodeConfig
@ -85,6 +86,20 @@ class InvalidLeaderStatus(StateException):
("The leader_status cannot be set to '%s' " +
"(valid values are: %s)") % (status, ", ".join(LEADER_STATUSES)))
class UnknownLeaderNodeId(StateException):
"""Raised when leader_Node_id is set to a non-existing node_id value."""
def __init__(self, leader_node_id):
super().__init__(
"The leader_node_id does not exist " +
"(leader_node_id=%s)" % leader_node_id)
class ActivePgbouncerConfigDoesNotExist(StateException):
"""Raised when active_pgbouncer_config is set to a non-existing path."""
def __init__(self, active_pgbouncer_config):
super().__init__(
"The active_pgbouncer_config file does not exist " +
"(path=%s)" % active_pgbouncer_config)
class InvalidNodeStatus(StateException):
"""Raised when the status for a node is set to an invalid value."""
def __init__(self, status):
@ -93,12 +108,23 @@ class InvalidNodeStatus(StateException):
"(valid values are: %s)") % (status, ", ".join(NODE_STATUSES)))
class State():
def __init__(self):
@staticmethod
def fromConfig(config, logger):
state = State(logger)
for node_id, settings in config.nodes.items():
node_config = NodeConfig(node_id)
for k, v in settings.items():
setattr(node_config, k, v)
state.add_node(node_config)
return state
def __init__(self, logger):
self.log = logger
self.modified = False
self._system_id = None
self._timeline_id = None
self._active_pgbouncer_config = None
self.leader_node_id = None
self._leader_node_id = None
self._leader_error = None
self._leader_status = LEADER_UNKNOWN
self.nodes = {}
@ -204,6 +230,26 @@ class State():
self._leader_status = status
self.modified = True
@property
def leader_node_id(self):
return self._leader_node_id
@leader_node_id.setter
def leader_node_id(self, leader_node_id):
"""Sets the id of the leader node."""
try:
node = self.get_node(leader_node_id)
except UnknownNodeRequested:
self.log.warning(
"The laeder_node_id value points to a non-existing " +
"node id: node config changed? (leader_node_id=%s)" %
leader_node_id)
raise UnknownLeaderNodeId(leader_node_id)
if self._leader_node_id == leader_node_id:
return
self._leader_node_id = leader_node_id
self.modified = True
@property
def leader_error(self):
return self._leader_error
@ -223,13 +269,22 @@ class State():
return self._active_pgbouncer_config
@active_pgbouncer_config.setter
def active_pgbouncer_config(self, pgbouncer_config):
def active_pgbouncer_config(self, active_pgbouncer_config):
"""Sets the pgbouncer configuration file that must be activated
in order to connect pgbouncer to the correct backend
PostgreSQL server."""
if self._active_pgbouncer_config == pgbouncer_config:
PostgreSQL server.
When the active config is set to a non-existing file (this can
happen when the node configuration changes), a warning will be
logged."""
if not os.path.exists(active_pgbouncer_config):
self.log.warning(
"The active_pgbouncer_config value points to a non-existing " +
"file: node config changed? (path=%s)" %
active_pgbouncer_config)
raise ActivePgbouncerConfigDoesNotExist(active_pgbouncer_config)
if self._active_pgbouncer_config == active_pgbouncer_config:
return
self._active_pgbouncer_config = pgbouncer_config
self._active_pgbouncer_config = active_pgbouncer_config
self.modified = True
def export(self):

View File

@ -6,6 +6,8 @@ from datetime import datetime
from os import rename
from os.path import isfile
from pgbouncemgr.logger import format_ex
from pgbouncemgr.state import \
UnknownLeaderNodeId, ActivePgbouncerConfigDoesNotExist
class StateStoreException(Exception):
@ -44,7 +46,18 @@ class StateStore():
"timeline_id",
"active_pgbouncer_config",
"leader_node_id"]:
setattr(self.state, key, loaded_state[key])
if key in loaded_state:
try:
setattr(self.state, key, loaded_state[key])
except (UnknownLeaderNodeId,
ActivePgbouncerConfigDoesNotExist):
# These exception can occur when the node configuration
# has changed and some fields are no longer value.
# In such case, we don't update the attributes here
# and simply ignore the specific errors. After the
# application has started, the correct values will be
# filled by the manager process.
pass
def store(self):
"""Store the current state in the state file. Returns True when

View File

@ -1,54 +0,0 @@
# -*- coding: utf-8 -*-
from pgbouncer.manager import DEFAULT_CONFIG, DEFAULT_LOG_FACILITY
class ArgsStub():
config = DEFAULT_CONFIG
log_facility = DEFAULT_LOG_FACILITY
verbose = False
debug = False
class LoggerStub():
def __init(self, ident, facility, debug, verbose):
pass
def debug(self, msg):
pass
def info(self, msg):
pass
def warning(self, msg):
pass
def error(self, msg):
pass
def format_ex(self, exception):
return "FMTEX"
class StateStoreStub():
old_state = None
test_with_nr_of_errors = 0
test_with_state = None
def load(self)
state = State()
def store(self, state):
new_state = json.dumps(state_to_store, sort_keys=True, indent=2)
if self.old_state and self.old_state == new_state:
return STORE_NOCHANGE
sel.old_state = new_state
if test_with_nr_of_errors:
test_with_nr_of_errors -= 1
self.err = "Storing state failed: just testing"
return STORE_ERROR
return STORE_UPDATED

134
tests/test_manager.py Normal file
View File

@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
import os
import shutil
import unittest
from pgbouncemgr.manager import *
CONFIG = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"testfiles", "basic.yaml")
CONFIG_WITH_STATE_FILE = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"testfiles", "basic_with_state_file.yaml")
STATE_FILE_TEMPLATE = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"testfiles", "state.json")
STATE_FILE = "/tmp/pgbouncemgr.state"
class ManagerTests(unittest.TestCase):
def test_CreateManagerUsingCommandLineOptions(self):
"""Check if a Manager object can be created, making use of some
command line arguments."""
mgr = Manager([
'-f', 'none', # no syslog
'-d', # debug (and verbose) enabled
'--config', CONFIG, # the config file to use
'-f', 'none' # the syslog facility to use, 'none' disables syslog
])
# Check the logging setup.
self.assertEqual(1, len(mgr.log))
console = mgr.log[0]
self.assertEqual('ConsoleLogTarget', type(console).__name__)
self.assertTrue(console.verbose_enabled)
self.assertTrue(console.debug_enabled)
def test_GivenNoStateFile_WhenCreatingManager_DefaultsAreUsed(self):
mgr = Manager(['--config', CONFIG])
# Check if the config file was read.
# If yes, then the state should be filled with data for the
# the configured nodes and all state should be in the initial
# configuration.
self.maxDiff = 5000
self.assertEqual({
'system_id': None,
'timeline_id': None,
'active_pgbouncer_config': None,
'leader_error': None,
'leader_node_id': None,
'leader_status': 'LEADER_UNKNOWN',
'nodes': {
'nodeA': {
'config': {
'host': '1.2.3.4',
'port': 8888,
'pgbouncer_config': None
},
'node_id': 'nodeA',
'is_leader': False,
'system_id': None,
'timeline_id': None,
'status': 'NODE_UNKNOWN',
'error': None
},
'nodeB': {
'config': {
'host': '2.3.4.5',
'port': 7777,
'pgbouncer_config': None
},
'node_id': 'nodeB',
'is_leader': False,
'system_id': None,
'timeline_id': None,
'status': 'NODE_UNKNOWN',
'error': None
}
}
}, mgr.state.export())
def test_GivenStateFile_WhenCreatingManager_StoredStateIsApplied(self):
mgr = Manager(['--config', CONFIG_WITH_STATE_FILE])
# Create the state file as expected by the loaded config.
shutil.copy(STATE_FILE_TEMPLATE, STATE_FILE)
# Check if the config file was read.
# If yes, then the state should be filled with data for the
# the configured nodes and all state should be in the initial
# configuration.
self.maxDiff = 5000
self.assertEqual({
'system_id': 'A',
'timeline_id': 42,
'active_pgbouncer_config': None, # because /myt/config.ini from the state does not exit
'leader_error': None,
'leader_node_id': 'nodeA',
'leader_status': 'LEADER_UNKNOWN',
'nodes': {
'nodeA': {
'config': {
'host': '1.2.3.4',
'port': 8888,
'pgbouncer_config': None
},
'node_id': 'nodeA',
'is_leader': True,
'system_id': None,
'timeline_id': None,
'status': 'NODE_UNKNOWN',
'error': None
},
'nodeB': {
'config': {
'host': '2.3.4.5',
'port': 7777,
'pgbouncer_config': None
},
'node_id': 'nodeB',
'is_leader': False,
'system_id': None,
'timeline_id': None,
'status': 'NODE_UNKNOWN',
'error': None
}
}
}, mgr.state.export())

View File

@ -2,6 +2,7 @@
import os
import unittest
from pgbouncemgr.logger import *
from pgbouncemgr.state import *
@ -12,7 +13,7 @@ PGBOUNCER_CONFIG = os.path.join(
class StateTests(unittest.TestCase):
def test_GivenFreshState_DefaultsAreSetCorrectly(self):
state = State()
state = State(Logger())
self.assertIsNone(state.system_id)
self.assertIsNone(state.timeline_id)
self.assertIsNone(state.active_pgbouncer_config)
@ -23,29 +24,29 @@ class StateTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_GivenFreshState_ModifiedFlagIsNotSet(self):
state = State()
state = State(Logger())
self.assertFalse(state.modified)
def test_GivenFreshState_IsCleanSlate_ReturnsTrue(self):
state = State()
state = State(Logger())
self.assertTrue(state.is_clean_slate())
def test_GivenStateWithSystemIdAssigned_IsCleanSlate_ReturnsFalse(self):
state = State()
state = State(Logger())
state.system_id = "whatever"
self.assertFalse(state.is_clean_slate())
def test_GivenFreshState_SetSystemId_SetsSystemId(self):
state = State()
state = State(Logger())
state.system_id = "booya"
self.assertEqual("booya", state.system_id)
def test_GivenStateWithSystemId_SetSameSystemId_IsOk(self):
state = State()
state = State(Logger())
state.system_id = "booya"
state.modified = False
state.system_id = "booya"
@ -53,7 +54,7 @@ class StateTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_GivenStateWithSystemId_SetDifferentSystemId_RaisesException(self):
state = State()
state = State(Logger())
state.system_id = "booya"
state.modified = False
@ -62,20 +63,20 @@ class StateTests(unittest.TestCase):
self.assertIn("from=booya, to=baayo", str(context.exception))
def test_GivenFreshState_SetTimelineId_SetsTimelineId(self):
state = State()
state = State(Logger())
state.timeline_id = 666
self.assertEqual(666, state.timeline_id)
self.assertTrue(state.modified)
def test_WithNonIntegerInput_SetTimelineId_RaisesException(self):
state = State()
state = State(Logger())
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 = State(Logger())
state.timeline_id = "50"
with self.assertRaises(TimelineIdLowered) as context:
@ -83,7 +84,7 @@ class StateTests(unittest.TestCase):
self.assertIn("(from=50, to=49)", str(context.exception))
def test_GivenStateWithTimelineId_SetSameTimelineId_IsOk(self):
state = State()
state = State(Logger())
state.timeline_id = 50
state.modified = False
@ -92,7 +93,7 @@ class StateTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_GivenStateWithTimelineId_SetHigherTimelineId_IsOk(self):
state = State()
state = State(Logger())
state.timeline_id = 50
state.modified = False
@ -102,7 +103,7 @@ class StateTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetLeaderStatus_SetsLeaderStatus(self):
state = State()
state = State(Logger())
state.modified = False
state.leader_status = LEADER_CONNECTED
@ -111,7 +112,7 @@ class StateTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetLeaderStatus_ToNonExistentStatus_RaisesException(self):
state = State()
state = State(Logger())
state.modified = False
with self.assertRaises(InvalidLeaderStatus) as context:
@ -119,7 +120,7 @@ class StateTests(unittest.TestCase):
self.assertIn("'gosh'", str(context.exception))
def test_SetLeaderError_SetsLeaderError(self):
state = State()
state = State(Logger())
state.modified = False
state.leader_error = "God disappeared in a puff of logic"
@ -136,14 +137,14 @@ class StateTests(unittest.TestCase):
class NodeCollectionTests(unittest.TestCase):
def test_WithNodeNotYetInState_AddNode_AddsNodeState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
self.assertEqual(1, node.node_id)
self.assertTrue(state.modified)
def test_WithNodeAlreadyInState_AddNode_RaisesException(self):
state = State()
state = State(Logger())
state.add_node(123)
with self.assertRaises(DuplicateNodeAdded) as context:
@ -151,13 +152,13 @@ class NodeCollectionTests(unittest.TestCase):
self.assertIn("duplicate node_id=123", str(context.exception))
def test_WithNodeNotInState_getNode_RaisesException(self):
state = State()
state = State(Logger())
with self.assertRaises(UnknownNodeRequested):
state.get_node("that does not exist")
def test_WithNodeInState_getNode_ReturnsNode(self):
state = State()
state = State(Logger())
node = state.add_node("that does exist")
node_from_state = state.get_node("that does exist")
@ -165,15 +166,15 @@ class NodeCollectionTests(unittest.TestCase):
self.assertEqual(node, node_from_state)
def test_WithUnknownNode_SetLeaderNode_RaisesException(self):
state1 = State()
state2 = State()
state1 = State(Logger())
state2 = State(Logger())
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()
state = State(Logger())
node = state.add_node(1)
node.timeline_id = 1
node.config.pgbouncer_config = PGBOUNCER_CONFIG
@ -183,7 +184,7 @@ class NodeCollectionTests(unittest.TestCase):
self.assertIn("node has no system_id", str(context.exception))
def test_WithNodeWithoutTimelineId_SetLeaderNode_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node(1)
node.system_id = "system"
node.config.pgbouncer_config = PGBOUNCER_CONFIG
@ -193,7 +194,7 @@ class NodeCollectionTests(unittest.TestCase):
self.assertIn("node has no timeline_id", str(context.exception))
def test_WithNodeWithoutPgbouncerConfig_SetLeaderNode_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node(1)
node.system_id = "a7d8a9347df789saghdfs"
node.timeline_id = 11111111111
@ -203,7 +204,7 @@ class NodeCollectionTests(unittest.TestCase):
self.assertIn("node has no pgbouncer_config", str(context.exception))
def test_SetLeaderNode_SetsLeaderNode_WithUnknownLeaderStatus(self):
state = State()
state = State(Logger())
node = state.add_node(1)
node.config.pgbouncer_config = PGBOUNCER_CONFIG
node.system_id = "SuperCluster"
@ -218,7 +219,7 @@ class NodeCollectionTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetLeaderNode_ToSameLeader_ResetsLeaderNode_WithUnknownLeaderStatus(self):
state = State()
state = State(Logger())
node = state.add_node(1)
node.config.pgbouncer_config = PGBOUNCER_CONFIG
node.system_id = "1.2.3.4.5.6.7.8.9.10.11"
@ -234,7 +235,7 @@ class NodeCollectionTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetLeaderNode_ToNodeWithDifferentSystemId_RaisesException(self):
state = State()
state = State(Logger())
node1 = state.add_node(1)
node1.config.pgbouncer_config = PGBOUNCER_CONFIG
node1.system_id = "systemA"
@ -249,7 +250,7 @@ class NodeCollectionTests(unittest.TestCase):
node2.promote()
def test_SetLeaderNode_ToNodeWithLowerTimelineId_RaisesException(self):
state = State()
state = State(Logger())
node1 = state.add_node(1)
node1.config.pgbouncer_config = PGBOUNCER_CONFIG
node1.system_id = "systemX"
@ -264,7 +265,7 @@ class NodeCollectionTests(unittest.TestCase):
node2.promote()
def test_SetLeaderNode_ToNodeWithSameOrHigherTimelineId_IsOk(self):
state = State()
state = State(Logger())
node1 = state.add_node(1)
node1.config.pgbouncer_config = PGBOUNCER_CONFIG
node1.system_id = "systemX"
@ -299,7 +300,7 @@ class NodeCollectionTests(unittest.TestCase):
class NodeTests(unittest.TestCase):
def test_WithNoneValue_SetSystemId_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node("break me")
state.modified = False
@ -309,7 +310,7 @@ class NodeTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_WithEmptyString_SetSystemId_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node("break me")
state.modified = False
@ -319,7 +320,7 @@ class NodeTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_SetSystemId_SetsSystemId_AndNotifiesChangeToState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
state.modified = False
@ -329,7 +330,7 @@ class NodeTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_WithNonIntegerInput_SetTimelineId_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node("break me")
with self.assertRaises(InvalidTimelineId) as context:
@ -337,7 +338,7 @@ class NodeTests(unittest.TestCase):
self.assertIn("invalid value=TARDIS", str(context.exception))
def test_SetTimelineId_SetsTimelineId_AndNotifiesChangeToState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
state.modified = False
@ -347,7 +348,7 @@ class NodeTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetStatus_SetsStatus_AndNotifiesChangeToState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
state.modified = False
@ -357,7 +358,7 @@ class NodeTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_WithInvalidStatus_SetStatus_RaisesException(self):
state = State()
state = State(Logger())
node = state.add_node(1)
state.modified = False
@ -367,7 +368,7 @@ class NodeTests(unittest.TestCase):
self.assertFalse(state.modified)
def test_SetError_ToString_SetsError_AndNotifiesChangeToState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
state.modified = False
@ -377,7 +378,7 @@ class NodeTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_SetError_ToNone_ClearsError_AndNotifiesChangeToState(self):
state = State()
state = State(Logger())
node = state.add_node(1)
node.set_error("Mouse lost its ball")
state.modified = False
@ -388,7 +389,7 @@ class NodeTests(unittest.TestCase):
self.assertTrue(state.modified)
def test_WhenNothingChanges_NoChangeIsNotifiedToState(self):
state = State()
state = State(Logger())
node = state.add_node("x")
node.system_id = "aaaaaaa"
node.timeline_id = 55
@ -408,7 +409,7 @@ class StateExportTests(unittest.TestCase):
def test_GivenFilledState_Export_ExportsDataForStore(self):
self.maxDiff = 5000;
state = State()
state = State(Logger())
node1 = state.add_node(1)
node1.config.pgbouncer_config = PGBOUNCER_CONFIG

View File

@ -4,6 +4,7 @@ import os
import json
import unittest
from tempfile import NamedTemporaryFile
from pgbouncemgr.logger import *
from pgbouncemgr.state import *
from pgbouncemgr.state_store import *
@ -15,7 +16,7 @@ def make_test_file_path(filename):
class StateStoreTests(unittest.TestCase):
def test_GivenNonExistingStateFile_OnLoad_StateStoreDoesNotLoadState(self):
state = State()
state = State(Logger())
before = state.export()
StateStore("/non-existent/state.json", state)
after = state.export()
@ -23,7 +24,7 @@ class StateStoreTests(unittest.TestCase):
self.assertEqual(before, after)
def test_GivenExistingStateFile_ContainingInvalidJson_OnLoad_StateStoreDoesNotLoadState(self):
state = State()
state = State(Logger())
before = state.export()
StateStore(make_test_file_path("invalid.json"), state)
after = state.export()
@ -31,14 +32,14 @@ class StateStoreTests(unittest.TestCase):
self.assertEqual(before, after)
def test_GivenExistingStateFile_OnLoad_StateStoreUpdatesState(self):
state = State()
state = State(Logger())
# These are the fields as
# These are the fields as defined in the state.json file.
expected = state.export()
expected["system_id"] = "A"
expected["timeline_id"] = 42
expected["leader_node_id"] = "NODE1"
expected["active_pgbouncer_config"] = "/my/config.ini"
expected["leader_node_id"] = None # because leader node id 'nodeA' does not exist.
expected["active_pgbouncer_config"] = None # because stored /my/config.ini does not exist.
StateStore(make_test_file_path("state.json"), state)
@ -46,7 +47,7 @@ class StateStoreTests(unittest.TestCase):
def test_GivenState_OnSave_StateStoreStoresState(self):
try:
state = State()
state = State(Logger())
tmpfile = NamedTemporaryFile(delete=False)
StateStore(tmpfile.name, state).store()

View File

@ -1,4 +1,7 @@
---
main:
state_file: /make/sure/no/state/file/exists/here
db_connection_defaults:
port: 8888
password: Wilmaaaaa!!!

View File

@ -0,0 +1,14 @@
---
main:
state_file: "/tmp/pgbouncemgr.state"
db_connection_defaults:
port: 8888
password: Wilmaaaaa!!!
nodes:
nodeA:
host: 1.2.3.4
nodeB:
host: 2.3.4.5
port: 7777

View File

@ -2,7 +2,7 @@
"active_pgbouncer_config": "/my/config.ini",
"system_id": "A",
"timeline_id": 42,
"leader_node_id": "NODE1",
"leader_node_id": "nodeA",
"any": "other",
"state": "properties",
"are": "ignored",