mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-01-27 10:20:10 +00:00
Added a new flag to ignore unsuccessful bind (#3138)
This commit is contained in:
@@ -238,7 +238,7 @@ Validate Patroni configuration
|
||||
|
||||
.. code:: text
|
||||
|
||||
patroni --validate-config [configfile]
|
||||
patroni --validate-config [configfile] [--ignore-listen-port | -i]
|
||||
|
||||
Description
|
||||
"""""""""""
|
||||
@@ -250,3 +250,6 @@ Parameters
|
||||
|
||||
``configfile``
|
||||
Full path to the configuration file to check. If not given or file does not exist, will try to read from the ``PATRONI_CONFIG_VARIABLE`` environment variable or, if not set, from the :ref:`Patroni environment variables <environment>`.
|
||||
|
||||
``--ignore-listen-port | -i``
|
||||
Optional flag to ignore bind failures for ``listen`` ports that are already in use when validating the ``configfile``.
|
||||
|
||||
@@ -241,6 +241,8 @@ def process_arguments() -> Namespace:
|
||||
* ``--validate-config`` -- used to validate the Patroni configuration file
|
||||
* ``--generate-config`` -- used to generate Patroni configuration from a running PostgreSQL instance
|
||||
* ``--generate-sample-config`` -- used to generate a sample Patroni configuration
|
||||
* ``--ignore-listen-port`` | ``-i`` -- used to ignore ``listen`` ports already in use.
|
||||
Can be used only with ``--validate-config``
|
||||
|
||||
.. note::
|
||||
If running with ``--generate-config``, ``--generate-sample-config`` or ``--validate-flag`` will exit
|
||||
@@ -259,6 +261,9 @@ def process_arguments() -> Namespace:
|
||||
help='Generate a Patroni yaml configuration file for a running instance')
|
||||
parser.add_argument('--dsn', help='Optional DSN string of the instance to be used as a source \
|
||||
for config generation. Superuser connection is required.')
|
||||
parser.add_argument('--ignore-listen-port', '-i', action='store_true',
|
||||
help='Ignore `listen` ports already in use.\
|
||||
Can only be used with --validate-config')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.generate_sample_config:
|
||||
@@ -269,7 +274,9 @@ def process_arguments() -> Namespace:
|
||||
sys.exit(0)
|
||||
elif args.validate_config:
|
||||
from patroni.config import Config, ConfigParseError
|
||||
from patroni.validator import schema
|
||||
from patroni.validator import populate_validate_params, schema
|
||||
|
||||
populate_validate_params(ignore_listen_port=args.ignore_listen_port)
|
||||
|
||||
try:
|
||||
Config(args.configfile, validator=schema)
|
||||
|
||||
@@ -17,6 +17,17 @@ from .exceptions import ConfigParseError
|
||||
from .log import type_logformat
|
||||
from .utils import data_directory_is_empty, get_major_version, parse_int, split_host_port
|
||||
|
||||
# Additional parameters to fine-tune validation process
|
||||
_validation_params: Dict[str, Any] = {}
|
||||
|
||||
|
||||
def populate_validate_params(ignore_listen_port: bool = False) -> None:
|
||||
"""Populate parameters used to fine-tune the validation of the Patroni config.
|
||||
|
||||
:param ignore_listen_port: ignore the bind failures for the ports marked as `listen`.
|
||||
"""
|
||||
_validation_params['ignore_listen_port'] = ignore_listen_port
|
||||
|
||||
|
||||
def validate_log_field(field: Union[str, Dict[str, Any], Any]) -> bool:
|
||||
"""Checks if log field is valid.
|
||||
@@ -137,7 +148,8 @@ def validate_host_port(host_port: str, listen: bool = False, multiple_hosts: boo
|
||||
s = socket.socket(proto[0][0], socket.SOCK_STREAM)
|
||||
try:
|
||||
if s.connect_ex((host, port)) == 0:
|
||||
if listen:
|
||||
# Do not raise an exception if ignore_listen_port is set to True.
|
||||
if listen and not _validation_params.get('ignore_listen_port', False):
|
||||
raise ConfigParseError("Port {} is already in use.".format(port))
|
||||
elif not listen:
|
||||
raise ConfigParseError("{} is not reachable".format(host_port))
|
||||
|
||||
@@ -8,7 +8,7 @@ from io import StringIO
|
||||
from unittest.mock import Mock, mock_open, patch
|
||||
|
||||
from patroni.dcs import dcs_modules
|
||||
from patroni.validator import Directory, schema, Schema
|
||||
from patroni.validator import Directory, populate_validate_params, schema, Schema
|
||||
|
||||
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
|
||||
config = {
|
||||
@@ -400,3 +400,38 @@ class TestValidator(unittest.TestCase):
|
||||
errors = schema(c)
|
||||
output = "\n".join(errors)
|
||||
self.assertEqual(['postgresql.bin_dir', 'raft.bind_addr', 'raft.self_addr'], parse_output(output))
|
||||
|
||||
@patch('socket.socket.connect_ex', Mock(return_value=0))
|
||||
def test_bound_port_checks_without_ignore(self, mock_out, mock_err):
|
||||
# When ignore_listen_port is False (default case), an error should be raised if the ports are already bound.
|
||||
c = copy.deepcopy(config)
|
||||
c['restapi']['listen'] = "127.0.0.1:8000"
|
||||
c['postgresql']['listen'] = "127.0.0.1:9000"
|
||||
c['raft']['self_addr'] = "127.0.0.2:9200"
|
||||
|
||||
populate_validate_params(ignore_listen_port=False)
|
||||
|
||||
errors = schema(c)
|
||||
output = "\n".join(errors)
|
||||
|
||||
self.assertEqual(['postgresql.bin_dir', 'postgresql.listen',
|
||||
'raft.bind_addr', 'restapi.listen'],
|
||||
parse_output(output))
|
||||
|
||||
@patch('socket.socket.connect_ex', Mock(return_value=0))
|
||||
def test_bound_port_checks_with_ignore(self, mock_out, mock_err):
|
||||
c = copy.deepcopy(config)
|
||||
c['restapi']['listen'] = "127.0.0.1:8000"
|
||||
c['postgresql']['listen'] = "127.0.0.1:9000"
|
||||
c['raft']['self_addr'] = "127.0.0.2:9200"
|
||||
c['raft']['bind_addr'] = "127.0.0.1:9300"
|
||||
|
||||
# Case: When ignore_listen_port is True, error should NOT be raised
|
||||
# even if the ports are already bound.
|
||||
populate_validate_params(ignore_listen_port=True)
|
||||
|
||||
errors = schema(c)
|
||||
output = "\n".join(errors)
|
||||
|
||||
self.assertEqual(['postgresql.bin_dir'],
|
||||
parse_output(output))
|
||||
|
||||
Reference in New Issue
Block a user