Skip to content

Commit c9322df

Browse files
authored
Added a new flag to ignore unsuccessful bind (patroni#3138)
1 parent b470ade commit c9322df

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

docs/patroni_configuration.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ Validate Patroni configuration
238238

239239
.. code:: text
240240
241-
patroni --validate-config [configfile]
241+
patroni --validate-config [configfile] [--ignore-listen-port | -i]
242242
243243
Description
244244
"""""""""""
@@ -250,3 +250,6 @@ Parameters
250250

251251
``configfile``
252252
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>`.
253+
254+
``--ignore-listen-port | -i``
255+
Optional flag to ignore bind failures for ``listen`` ports that are already in use when validating the ``configfile``.

patroni/__main__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ def process_arguments() -> Namespace:
241241
* ``--validate-config`` -- used to validate the Patroni configuration file
242242
* ``--generate-config`` -- used to generate Patroni configuration from a running PostgreSQL instance
243243
* ``--generate-sample-config`` -- used to generate a sample Patroni configuration
244+
* ``--ignore-listen-port`` | ``-i`` -- used to ignore ``listen`` ports already in use.
245+
Can be used only with ``--validate-config``
244246
245247
.. note::
246248
If running with ``--generate-config``, ``--generate-sample-config`` or ``--validate-flag`` will exit
@@ -259,6 +261,9 @@ def process_arguments() -> Namespace:
259261
help='Generate a Patroni yaml configuration file for a running instance')
260262
parser.add_argument('--dsn', help='Optional DSN string of the instance to be used as a source \
261263
for config generation. Superuser connection is required.')
264+
parser.add_argument('--ignore-listen-port', '-i', action='store_true',
265+
help='Ignore `listen` ports already in use.\
266+
Can only be used with --validate-config')
262267
args = parser.parse_args()
263268

264269
if args.generate_sample_config:
@@ -269,7 +274,9 @@ def process_arguments() -> Namespace:
269274
sys.exit(0)
270275
elif args.validate_config:
271276
from patroni.config import Config, ConfigParseError
272-
from patroni.validator import schema
277+
from patroni.validator import populate_validate_params, schema
278+
279+
populate_validate_params(ignore_listen_port=args.ignore_listen_port)
273280

274281
try:
275282
Config(args.configfile, validator=schema)

patroni/validator.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
from .log import type_logformat
1818
from .utils import data_directory_is_empty, get_major_version, parse_int, split_host_port
1919

20+
# Additional parameters to fine-tune validation process
21+
_validation_params: Dict[str, Any] = {}
22+
23+
24+
def populate_validate_params(ignore_listen_port: bool = False) -> None:
25+
"""Populate parameters used to fine-tune the validation of the Patroni config.
26+
27+
:param ignore_listen_port: ignore the bind failures for the ports marked as `listen`.
28+
"""
29+
_validation_params['ignore_listen_port'] = ignore_listen_port
30+
2031

2132
def validate_log_field(field: Union[str, Dict[str, Any], Any]) -> bool:
2233
"""Checks if log field is valid.
@@ -137,7 +148,8 @@ def validate_host_port(host_port: str, listen: bool = False, multiple_hosts: boo
137148
s = socket.socket(proto[0][0], socket.SOCK_STREAM)
138149
try:
139150
if s.connect_ex((host, port)) == 0:
140-
if listen:
151+
# Do not raise an exception if ignore_listen_port is set to True.
152+
if listen and not _validation_params.get('ignore_listen_port', False):
141153
raise ConfigParseError("Port {} is already in use.".format(port))
142154
elif not listen:
143155
raise ConfigParseError("{} is not reachable".format(host_port))

tests/test_validator.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from unittest.mock import Mock, mock_open, patch
99

1010
from patroni.dcs import dcs_modules
11-
from patroni.validator import Directory, schema, Schema
11+
from patroni.validator import Directory, populate_validate_params, schema, Schema
1212

1313
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
1414
config = {
@@ -400,3 +400,38 @@ def test_json_log_format(self, *args):
400400
errors = schema(c)
401401
output = "\n".join(errors)
402402
self.assertEqual(['postgresql.bin_dir', 'raft.bind_addr', 'raft.self_addr'], parse_output(output))
403+
404+
@patch('socket.socket.connect_ex', Mock(return_value=0))
405+
def test_bound_port_checks_without_ignore(self, mock_out, mock_err):
406+
# When ignore_listen_port is False (default case), an error should be raised if the ports are already bound.
407+
c = copy.deepcopy(config)
408+
c['restapi']['listen'] = "127.0.0.1:8000"
409+
c['postgresql']['listen'] = "127.0.0.1:9000"
410+
c['raft']['self_addr'] = "127.0.0.2:9200"
411+
412+
populate_validate_params(ignore_listen_port=False)
413+
414+
errors = schema(c)
415+
output = "\n".join(errors)
416+
417+
self.assertEqual(['postgresql.bin_dir', 'postgresql.listen',
418+
'raft.bind_addr', 'restapi.listen'],
419+
parse_output(output))
420+
421+
@patch('socket.socket.connect_ex', Mock(return_value=0))
422+
def test_bound_port_checks_with_ignore(self, mock_out, mock_err):
423+
c = copy.deepcopy(config)
424+
c['restapi']['listen'] = "127.0.0.1:8000"
425+
c['postgresql']['listen'] = "127.0.0.1:9000"
426+
c['raft']['self_addr'] = "127.0.0.2:9200"
427+
c['raft']['bind_addr'] = "127.0.0.1:9300"
428+
429+
# Case: When ignore_listen_port is True, error should NOT be raised
430+
# even if the ports are already bound.
431+
populate_validate_params(ignore_listen_port=True)
432+
433+
errors = schema(c)
434+
output = "\n".join(errors)
435+
436+
self.assertEqual(['postgresql.bin_dir'],
437+
parse_output(output))

0 commit comments

Comments
 (0)