Added a new flag to ignore unsuccessful bind (#3138)

This commit is contained in:
Sahil Naphade
2024-08-29 00:39:38 -07:00
committed by GitHub
parent b470ade20e
commit c9322df095
4 changed files with 61 additions and 4 deletions

View File

@@ -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``.

View File

@@ -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)

View File

@@ -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))

View File

@@ -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))