mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Remove python 2.7 support (#2571)
- get rid from 2.7 specific modules: `six`, `ipaddress` - use Python3 unpacking operator - use `shutil.which()` instead of `find_executable()`
This commit is contained in:
@@ -7,7 +7,6 @@ import psutil
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
import six
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -17,12 +16,11 @@ import yaml
|
|||||||
|
|
||||||
import patroni.psycopg as psycopg
|
import patroni.psycopg as psycopg
|
||||||
|
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from patroni.request import PatroniRequest
|
from patroni.request import PatroniRequest
|
||||||
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class AbstractController(abc.ABC):
|
||||||
class AbstractController(object):
|
|
||||||
|
|
||||||
def __init__(self, context, name, work_directory, output_dir):
|
def __init__(self, context, name, work_directory, output_dir):
|
||||||
self._context = context
|
self._context = context
|
||||||
|
|||||||
+9
-15
@@ -7,15 +7,14 @@ import traceback
|
|||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ipaddress import ip_address, ip_network as _ip_network
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
from ipaddress import ip_address, ip_network
|
||||||
from six.moves.socketserver import ThreadingMixIn
|
from socketserver import ThreadingMixIn
|
||||||
from six.moves.urllib_parse import urlparse, parse_qs
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
|
||||||
from . import psycopg
|
from . import psycopg
|
||||||
from .exceptions import PostgresConnectionException, PostgresException
|
from .exceptions import PostgresConnectionException, PostgresException
|
||||||
@@ -26,10 +25,6 @@ from .utils import deep_compare, enable_keepalive, parse_bool, patch_config, Ret
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def ip_network(value):
|
|
||||||
return _ip_network(value.decode('utf-8') if six.PY2 else value, False)
|
|
||||||
|
|
||||||
|
|
||||||
class RestApiHandler(BaseHTTPRequestHandler):
|
class RestApiHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
def _write_status_code_only(self, status_code):
|
def _write_status_code_only(self, status_code):
|
||||||
@@ -172,7 +167,7 @@ class RestApiHandler(BaseHTTPRequestHandler):
|
|||||||
if instance_tag_value is None:
|
if instance_tag_value is None:
|
||||||
status_code = 503
|
status_code = 503
|
||||||
break
|
break
|
||||||
if not isinstance(instance_tag_value, six.string_types):
|
if not isinstance(instance_tag_value, str):
|
||||||
instance_tag_value = str(instance_tag_value).lower()
|
instance_tag_value = str(instance_tag_value).lower()
|
||||||
if instance_tag_value != qs_value:
|
if instance_tag_value != qs_value:
|
||||||
status_code = 503
|
status_code = 503
|
||||||
@@ -768,7 +763,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
|||||||
def __resolve_ips(host, port):
|
def __resolve_ips(host, port):
|
||||||
try:
|
try:
|
||||||
for _, _, _, _, sa in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP):
|
for _, _, _, _, sa in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM, socket.IPPROTO_TCP):
|
||||||
yield ip_network(sa[0])
|
yield ip_network(sa[0], False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Failed to resolve %s: %r', host, e)
|
logger.error('Failed to resolve %s: %r', host, e)
|
||||||
|
|
||||||
@@ -789,8 +784,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
|||||||
|
|
||||||
def check_access(self, rh):
|
def check_access(self, rh):
|
||||||
if self.__allowlist or self.__allowlist_include_members:
|
if self.__allowlist or self.__allowlist_include_members:
|
||||||
incoming_ip = rh.client_address[0]
|
incoming_ip = ip_address(rh.client_address[0])
|
||||||
incoming_ip = ip_address(incoming_ip.decode('utf-8') if six.PY2 else incoming_ip)
|
|
||||||
if not any(incoming_ip in net for net in self.__allowlist + tuple(self.__members_ips())):
|
if not any(incoming_ip in net for net in self.__allowlist + tuple(self.__members_ips())):
|
||||||
return rh._write_response(403, 'Access is denied')
|
return rh._write_response(403, 'Access is denied')
|
||||||
|
|
||||||
@@ -915,7 +909,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
|||||||
for v in value:
|
for v in value:
|
||||||
if '/' in v: # netmask
|
if '/' in v: # netmask
|
||||||
try:
|
try:
|
||||||
yield ip_network(v)
|
yield ip_network(v, False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Invalid value "%s" in the allowlist: %r', v, e)
|
logger.error('Invalid value "%s" in the allowlist: %r', v, e)
|
||||||
else: # ip or hostname, try to resolve it
|
else: # ip or hostname, try to resolve it
|
||||||
@@ -935,7 +929,7 @@ class RestApiServer(ThreadingMixIn, HTTPServer, Thread):
|
|||||||
self.http_extra_headers = config.get('http_extra_headers') or {}
|
self.http_extra_headers = config.get('http_extra_headers') or {}
|
||||||
self.http_extra_headers.update((config.get('https_extra_headers') or {}) if ssl_options.get('certfile') else {})
|
self.http_extra_headers.update((config.get('https_extra_headers') or {}) if ssl_options.get('certfile') else {})
|
||||||
|
|
||||||
if isinstance(config.get('verify_client'), six.string_types):
|
if isinstance(config.get('verify_client'), str):
|
||||||
ssl_options['verify_client'] = config['verify_client'].lower()
|
ssl_options['verify_client'] = config['verify_client'].lower()
|
||||||
|
|
||||||
if self.__listen != config['listen'] or self.__ssl_options != ssl_options or self._received_new_cert:
|
if self.__listen != config['listen'] or self.__ssl_options != ssl_options or self._received_new_cert:
|
||||||
|
|||||||
+2
-3
@@ -2,7 +2,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import six
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -408,8 +407,8 @@ class Config(object):
|
|||||||
config = self._safe_copy_dynamic_configuration(dynamic_configuration)
|
config = self._safe_copy_dynamic_configuration(dynamic_configuration)
|
||||||
for name, value in local_configuration.items():
|
for name, value in local_configuration.items():
|
||||||
if name == 'citus': # remove invalid citus configuration
|
if name == 'citus': # remove invalid citus configuration
|
||||||
if isinstance(value, dict) and isinstance(value.get('group'), six.integer_types)\
|
if isinstance(value, dict) and isinstance(value.get('group'), int)\
|
||||||
and isinstance(value.get('database'), six.string_types):
|
and isinstance(value.get('database'), str):
|
||||||
config[name] = value
|
config[name] = value
|
||||||
elif name == 'postgresql':
|
elif name == 'postgresql':
|
||||||
for name, value in (value or {}).items():
|
for name, value in (value or {}).items():
|
||||||
|
|||||||
+10
-11
@@ -4,17 +4,17 @@ Patroni Control
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
import codecs
|
import codecs
|
||||||
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import dateutil.tz
|
import dateutil.tz
|
||||||
import copy
|
|
||||||
import difflib
|
import difflib
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import six
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -25,7 +25,7 @@ from click import ClickException
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from prettytable import ALL, FRAME, PrettyTable
|
from prettytable import ALL, FRAME, PrettyTable
|
||||||
from six.moves.urllib_parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ydiff import markup_to_pager, PatchStream
|
from ydiff import markup_to_pager, PatchStream
|
||||||
@@ -35,7 +35,7 @@ except ImportError: # pragma: no cover
|
|||||||
from .dcs import get_dcs as _get_dcs
|
from .dcs import get_dcs as _get_dcs
|
||||||
from .exceptions import PatroniException
|
from .exceptions import PatroniException
|
||||||
from .postgresql.misc import postgres_version_to_int
|
from .postgresql.misc import postgres_version_to_int
|
||||||
from .utils import cluster_as_json, find_executable, patch_config, polling_loop, is_standby_cluster
|
from .utils import cluster_as_json, patch_config, polling_loop, is_standby_cluster
|
||||||
from .request import PatroniRequest
|
from .request import PatroniRequest
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ def print_output(columns, rows, alignment=None, fmt='pretty', header=None, delim
|
|||||||
for r in ([columns] if columns else []) + rows:
|
for r in ([columns] if columns else []) + rows:
|
||||||
click.echo(delimiter.join(map(str, r)))
|
click.echo(delimiter.join(map(str, r)))
|
||||||
else:
|
else:
|
||||||
hrules = ALL if any(any(isinstance(c, six.string_types) and '\n' in c for c in r) for r in rows) else FRAME
|
hrules = ALL if any(any(isinstance(c, str) and '\n' in c for c in r) for r in rows) else FRAME
|
||||||
table = PatronictlPrettyTable(header, columns, hrules=hrules)
|
table = PatronictlPrettyTable(header, columns, hrules=hrules)
|
||||||
table.align = 'l'
|
table.align = 'l'
|
||||||
for k, v in (alignment or {}).items():
|
for k, v in (alignment or {}).items():
|
||||||
@@ -875,7 +875,7 @@ def output_members(obj, cluster, name, extended=False, fmt='pretty', group=None)
|
|||||||
member.update(cluster=name, member=member['name'], group=g,
|
member.update(cluster=name, member=member['name'], group=g,
|
||||||
host=member.get('host', ''), tl=member.get('timeline', ''),
|
host=member.get('host', ''), tl=member.get('timeline', ''),
|
||||||
role=member['role'].replace('_', ' ').title(),
|
role=member['role'].replace('_', ' ').title(),
|
||||||
lag_in_mb=round(lag/1024/1024) if isinstance(lag, six.integer_types) else lag,
|
lag_in_mb=round(lag/1024/1024) if isinstance(lag, int) else lag,
|
||||||
pending_restart='*' if member.get('pending_restart') else '')
|
pending_restart='*' if member.get('pending_restart') else '')
|
||||||
|
|
||||||
if append_port and member['host'] and member.get('port'):
|
if append_port and member['host'] and member.get('port'):
|
||||||
@@ -1084,8 +1084,7 @@ def show_diff(before_editing, after_editing):
|
|||||||
if sys.stdout.isatty():
|
if sys.stdout.isatty():
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
for line in unified_diff:
|
for line in unified_diff:
|
||||||
# Force cast to unicode as difflib on Python 2.7 returns a mix of unicode and str.
|
buf.write(str(line))
|
||||||
buf.write(six.text_type(line))
|
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
class opts:
|
class opts:
|
||||||
@@ -1093,10 +1092,10 @@ def show_diff(before_editing, after_editing):
|
|||||||
width = 80
|
width = 80
|
||||||
tab_width = 8
|
tab_width = 8
|
||||||
wrap = True
|
wrap = True
|
||||||
if find_executable('less'):
|
if shutil.which('less'):
|
||||||
pager = None
|
pager = None
|
||||||
else:
|
else:
|
||||||
pager = 'more.com' if sys.platform == 'win32' else 'more'
|
pager = os.path.basename(shutil.which('more') or 'more')
|
||||||
pager_options = None
|
pager_options = None
|
||||||
|
|
||||||
markup_to_pager(PatchStream(buf), opts)
|
markup_to_pager(PatchStream(buf), opts)
|
||||||
@@ -1184,7 +1183,7 @@ def invoke_editor(before_editing, cluster_name):
|
|||||||
editor_cmd = os.environ.get('EDITOR')
|
editor_cmd = os.environ.get('EDITOR')
|
||||||
if not editor_cmd:
|
if not editor_cmd:
|
||||||
for editor in ('editor', 'vi'):
|
for editor in ('editor', 'vi'):
|
||||||
editor_cmd = find_executable(editor)
|
editor_cmd = shutil.which(editor)
|
||||||
if editor_cmd:
|
if editor_cmd:
|
||||||
logging.debug('Setting fallback editor_cmd=%s', editor)
|
logging.debug('Setting fallback editor_cmd=%s', editor)
|
||||||
break
|
break
|
||||||
|
|||||||
+1
-3
@@ -3,14 +3,12 @@ from __future__ import print_function
|
|||||||
import abc
|
import abc
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class AbstractPatroniDaemon(abc.ABC):
|
||||||
class AbstractPatroniDaemon(object):
|
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
from patroni.log import PatroniLogger
|
from patroni.log import PatroniLogger
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from random import randint
|
from random import randint
|
||||||
from six.moves.urllib_parse import urlparse, urlunparse, parse_qsl
|
|
||||||
from threading import Event, Lock
|
from threading import Event, Lock
|
||||||
|
from urllib.parse import urlparse, urlunparse, parse_qsl
|
||||||
|
|
||||||
from ..exceptions import PatroniFatalException
|
from ..exceptions import PatroniFatalException
|
||||||
from ..utils import deep_compare, parse_bool, uri
|
from ..utils import deep_compare, parse_bool, uri
|
||||||
@@ -654,8 +653,7 @@ def catch_return_false_exception(func):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class AbstractDCS(abc.ABC):
|
||||||
class AbstractDCS(object):
|
|
||||||
|
|
||||||
_INITIALIZE = 'initialize'
|
_INITIALIZE = 'initialize'
|
||||||
_CONFIG = 'config'
|
_CONFIG = 'config'
|
||||||
@@ -676,7 +674,7 @@ class AbstractDCS(object):
|
|||||||
"""
|
"""
|
||||||
self._name = config['name']
|
self._name = config['name']
|
||||||
self._base_path = re.sub('/+', '/', '/'.join(['', config.get('namespace', 'service'), config['scope']]))
|
self._base_path = re.sub('/+', '/', '/'.join(['', config.get('namespace', 'service'), config['scope']]))
|
||||||
self._citus_group = str(config['group']) if isinstance(config.get('group'), six.integer_types) else None
|
self._citus_group = str(config['group']) if isinstance(config.get('group'), int) else None
|
||||||
self._set_loop_wait(config.get('loop_wait', 10))
|
self._set_loop_wait(config.get('loop_wait', 10))
|
||||||
|
|
||||||
self._ctl = bool(config.get('patronictl', False))
|
self._ctl = bool(config.get('patronictl', False))
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import urllib3
|
|||||||
|
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict, namedtuple
|
||||||
from consul import ConsulException, NotFound, base
|
from consul import ConsulException, NotFound, base
|
||||||
|
from http.client import HTTPException
|
||||||
from urllib3.exceptions import HTTPError
|
from urllib3.exceptions import HTTPError
|
||||||
from six.moves.urllib.parse import urlencode, urlparse, quote
|
from urllib.parse import urlencode, urlparse, quote
|
||||||
from six.moves.http_client import HTTPException
|
|
||||||
|
|
||||||
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
||||||
TimelineHistory, ReturnFalseException, catch_return_false_exception, citus_group_re
|
TimelineHistory, ReturnFalseException, catch_return_false_exception, citus_group_re
|
||||||
|
|||||||
+7
-9
@@ -6,7 +6,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import urllib3.util.connection
|
import urllib3.util.connection
|
||||||
import random
|
import random
|
||||||
import six
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -14,12 +13,12 @@ from collections import defaultdict
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from dns.exception import DNSException
|
from dns.exception import DNSException
|
||||||
from dns import resolver
|
from dns import resolver
|
||||||
|
from http.client import HTTPException
|
||||||
|
from queue import Queue
|
||||||
|
from threading import Thread
|
||||||
|
from urllib.parse import urlparse
|
||||||
from urllib3 import Timeout
|
from urllib3 import Timeout
|
||||||
from urllib3.exceptions import HTTPError, ReadTimeoutError, ProtocolError
|
from urllib3.exceptions import HTTPError, ReadTimeoutError, ProtocolError
|
||||||
from six.moves.queue import Queue
|
|
||||||
from six.moves.http_client import HTTPException
|
|
||||||
from six.moves.urllib_parse import urlparse
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
||||||
TimelineHistory, ReturnFalseException, catch_return_false_exception, citus_group_re
|
TimelineHistory, ReturnFalseException, catch_return_false_exception, citus_group_re
|
||||||
@@ -86,8 +85,7 @@ class DnsCachingResolver(Thread):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class AbstractEtcdClientWithFailover(abc.ABC, etcd.Client):
|
||||||
class AbstractEtcdClientWithFailover(etcd.Client):
|
|
||||||
|
|
||||||
def __init__(self, config, dns_resolver, cache_ttl=300):
|
def __init__(self, config, dns_resolver, cache_ttl=300):
|
||||||
self._dns_resolver = dns_resolver
|
self._dns_resolver = dns_resolver
|
||||||
@@ -496,12 +494,12 @@ class AbstractEtcd(AbstractDCS):
|
|||||||
default_port = config.pop('port', 2379)
|
default_port = config.pop('port', 2379)
|
||||||
protocol = config.get('protocol', 'http')
|
protocol = config.get('protocol', 'http')
|
||||||
|
|
||||||
if isinstance(hosts, six.string_types):
|
if isinstance(hosts, str):
|
||||||
hosts = hosts.split(',')
|
hosts = hosts.split(',')
|
||||||
|
|
||||||
config['hosts'] = []
|
config['hosts'] = []
|
||||||
for value in hosts:
|
for value in hosts:
|
||||||
if isinstance(value, six.string_types):
|
if isinstance(value, str):
|
||||||
config['hosts'].append(uri(protocol, split_host_port(value.strip(), default_port)))
|
config['hosts'].append(uri(protocol, split_host_port(value.strip(), default_port)))
|
||||||
elif 'host' in config:
|
elif 'host' in config:
|
||||||
host, port = split_host_port(config['host'], 2379)
|
host, port = split_host_port(config['host'], 2379)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import etcd
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@@ -177,20 +176,6 @@ class Etcd3Client(AbstractEtcdClientWithFailover):
|
|||||||
self.version_prefix = '/v3beta'
|
self.version_prefix = '/v3beta'
|
||||||
super(Etcd3Client, self).__init__(config, dns_resolver, cache_ttl)
|
super(Etcd3Client, self).__init__(config, dns_resolver, cache_ttl)
|
||||||
|
|
||||||
if six.PY2: # pragma: no cover
|
|
||||||
# Old grpc-gateway sometimes sends double 'transfer-encoding: chunked' headers,
|
|
||||||
# what breaks the old (python2.7) httplib.HTTPConnection (it closes the socket).
|
|
||||||
def dedup_addheader(httpm, key, value):
|
|
||||||
prev = httpm.dict.get(key)
|
|
||||||
if prev is None:
|
|
||||||
httpm.dict[key] = value
|
|
||||||
elif key != 'transfer-encoding' or prev != value:
|
|
||||||
combined = ", ".join((prev, value))
|
|
||||||
httpm.dict[key] = combined
|
|
||||||
|
|
||||||
import httplib
|
|
||||||
httplib.HTTPMessage.addheader = dedup_addheader
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
except AuthFailed as e:
|
except AuthFailed as e:
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ class Exhibitor(ZooKeeper):
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
interval = config.get('poll_interval', 300)
|
interval = config.get('poll_interval', 300)
|
||||||
self._ensemble_provider = ExhibitorEnsembleProvider(config['hosts'], config['port'], poll_interval=interval)
|
self._ensemble_provider = ExhibitorEnsembleProvider(config['hosts'], config['port'], poll_interval=interval)
|
||||||
config = config.copy()
|
super(Exhibitor, self).__init__({**config, 'hosts': self._ensemble_provider.zookeeper_hosts})
|
||||||
config['hosts'] = self._ensemble_provider.zookeeper_hosts
|
|
||||||
super(Exhibitor, self).__init__(config)
|
|
||||||
|
|
||||||
def _load_cluster(self, path, loader):
|
def _load_cluster(self, path, loader):
|
||||||
if self._ensemble_provider.poll():
|
if self._ensemble_provider.poll():
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import six
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import urllib3
|
import urllib3
|
||||||
@@ -15,11 +14,10 @@ import yaml
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from urllib3 import Timeout
|
from http.client import HTTPException
|
||||||
from urllib3.exceptions import HTTPError
|
|
||||||
from six.moves.http_client import HTTPException
|
|
||||||
from threading import Condition, Lock, Thread
|
from threading import Condition, Lock, Thread
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
from urllib3.exceptions import HTTPError
|
||||||
|
|
||||||
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
from . import AbstractDCS, Cluster, ClusterConfig, Failover, Leader, Member, SyncState,\
|
||||||
TimelineHistory, CITUS_COORDINATOR_GROUP_ID, citus_group_re
|
TimelineHistory, CITUS_COORDINATOR_GROUP_ID, citus_group_re
|
||||||
@@ -177,7 +175,7 @@ class K8sObject(object):
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
# we know that `annotations` and `labels` are dicts and therefore don't want to convert them into K8sObject
|
# we know that `annotations` and `labels` are dicts and therefore don't want to convert them into K8sObject
|
||||||
return value if parent in {'annotations', 'labels'} and \
|
return value if parent in {'annotations', 'labels'} and \
|
||||||
all(isinstance(v, six.string_types) for v in value.values()) else cls(value)
|
all(isinstance(v, str) for v in value.values()) else cls(value)
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
return [cls._wrap(None, v) for v in value]
|
return [cls._wrap(None, v) for v in value]
|
||||||
else:
|
else:
|
||||||
@@ -377,7 +375,7 @@ class K8sClient(object):
|
|||||||
api_servers = len(api_servers_cache)
|
api_servers = len(api_servers_cache)
|
||||||
|
|
||||||
if timeout:
|
if timeout:
|
||||||
if isinstance(timeout, six.integer_types + (float,)):
|
if isinstance(timeout, (int, float)):
|
||||||
timeout = urllib3.Timeout(total=timeout)
|
timeout = urllib3.Timeout(total=timeout)
|
||||||
elif isinstance(timeout, tuple) and len(timeout) == 2:
|
elif isinstance(timeout, tuple) and len(timeout) == 2:
|
||||||
timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1])
|
timeout = urllib3.Timeout(connect=timeout[0], read=timeout[1])
|
||||||
@@ -576,7 +574,7 @@ class ObjectCache(Thread):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def _watch(self, resource_version):
|
def _watch(self, resource_version):
|
||||||
return self._func(_request_timeout=(self._retry.deadline, Timeout.DEFAULT_TIMEOUT),
|
return self._func(_request_timeout=(self._retry.deadline, urllib3.Timeout.DEFAULT_TIMEOUT),
|
||||||
_preload_content=False, watch=True, resource_version=resource_version)
|
_preload_content=False, watch=True, resource_version=resource_version)
|
||||||
|
|
||||||
def set(self, name, value):
|
def set(self, name, value):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import select
|
import select
|
||||||
import six
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from kazoo.client import KazooClient, KazooState, KazooRetry
|
from kazoo.client import KazooClient, KazooState, KazooRetry
|
||||||
@@ -63,10 +62,8 @@ class PatroniSequentialThreadingHandler(SequentialThreadingHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return super(PatroniSequentialThreadingHandler, self).select(*args, **kwargs)
|
return super(PatroniSequentialThreadingHandler, self).select(*args, **kwargs)
|
||||||
except IOError as e:
|
|
||||||
raise (select.error(e.errno, e.strerror) if six.PY2 else e)
|
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
raise (e if six.PY2 and isinstance(e, TypeError) else select.error(9, str(e)))
|
raise select.error(9, str(e))
|
||||||
|
|
||||||
|
|
||||||
class PatroniKazooClient(KazooClient):
|
class PatroniKazooClient(KazooClient):
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,6 @@ import datetime
|
|||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
@@ -880,7 +879,7 @@ class Ha(object):
|
|||||||
not_allowed_reason = st.failover_limitation()
|
not_allowed_reason = st.failover_limitation()
|
||||||
if not_allowed_reason:
|
if not_allowed_reason:
|
||||||
logger.info('Member %s is %s', st.member.name, not_allowed_reason)
|
logger.info('Member %s is %s', st.member.name, not_allowed_reason)
|
||||||
elif not isinstance(st.wal_position, six.integer_types):
|
elif not isinstance(st.wal_position, int):
|
||||||
logger.info('Member %s does not report wal_position', st.member.name)
|
logger.info('Member %s does not report wal_position', st.member.name)
|
||||||
elif cluster_lsn and st.wal_position < cluster_lsn or\
|
elif cluster_lsn and st.wal_position < cluster_lsn or\
|
||||||
not cluster_lsn and self.is_lagging(st.wal_position):
|
not cluster_lsn and self.is_lagging(st.wal_position):
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import sys
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from patroni.utils import deep_compare
|
from patroni.utils import deep_compare
|
||||||
from six.moves.queue import Queue, Full
|
from queue import Queue, Full
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import six
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -473,7 +472,7 @@ class Postgresql(object):
|
|||||||
return prev
|
return prev
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Exception when parsing WAL pg_%sdump output: %r', self.wal_name, e)
|
logger.error('Exception when parsing WAL pg_%sdump output: %r', self.wal_name, e)
|
||||||
if isinstance(checkpoint_lsn, six.integer_types):
|
if isinstance(checkpoint_lsn, int):
|
||||||
return checkpoint_lsn
|
return checkpoint_lsn
|
||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
@@ -866,8 +865,7 @@ class Postgresql(object):
|
|||||||
# Don't try to call pg_controldata during backup restore
|
# Don't try to call pg_controldata during backup restore
|
||||||
if self._version_file_exists() and self.state != 'creating replica':
|
if self._version_file_exists() and self.state != 'creating replica':
|
||||||
try:
|
try:
|
||||||
env = os.environ.copy()
|
env = {**os.environ, 'LANG': 'C', 'LC_ALL': 'C'}
|
||||||
env.update(LANG='C', LC_ALL='C')
|
|
||||||
data = subprocess.check_output([self.pgcommand('pg_controldata'), self._data_dir], env=env)
|
data = subprocess.check_output([self.pgcommand('pg_controldata'), self._data_dir], env=env)
|
||||||
if data:
|
if data:
|
||||||
data = filter(lambda e: ':' in e, data.decode('utf-8').splitlines())
|
data = filter(lambda e: ':' in e, data.decode('utf-8').splitlines())
|
||||||
@@ -879,8 +877,7 @@ class Postgresql(object):
|
|||||||
|
|
||||||
def waldump(self, timeline, lsn, limit):
|
def waldump(self, timeline, lsn, limit):
|
||||||
cmd = self.pgcommand('pg_{0}dump'.format(self.wal_name))
|
cmd = self.pgcommand('pg_{0}dump'.format(self.wal_name))
|
||||||
env = os.environ.copy()
|
env = {**os.environ, 'LANG': 'C', 'LC_ALL': 'C', 'PGDATA': self._data_dir}
|
||||||
env.update(LANG='C', LC_ALL='C', PGDATA=self._data_dir)
|
|
||||||
try:
|
try:
|
||||||
waldump = subprocess.Popen([cmd, '-t', str(timeline), '-s', str(lsn), '-n', str(limit)],
|
waldump = subprocess.Popen([cmd, '-t', str(timeline), '-s', str(lsn), '-n', str(limit)],
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import shlex
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from six import string_types
|
|
||||||
|
|
||||||
from ..dcs import RemoteMember
|
from ..dcs import RemoteMember
|
||||||
from ..psycopg import quote_ident, quote_literal
|
from ..psycopg import quote_ident, quote_literal
|
||||||
from ..utils import deep_compare
|
from ..utils import deep_compare
|
||||||
@@ -43,11 +41,11 @@ class Bootstrap(object):
|
|||||||
user_options.append('--{0}={1}'.format(k, v))
|
user_options.append('--{0}={1}'.format(k, v))
|
||||||
elif isinstance(options, list):
|
elif isinstance(options, list):
|
||||||
for opt in options:
|
for opt in options:
|
||||||
if isinstance(opt, string_types) and option_is_allowed(opt):
|
if isinstance(opt, str) and option_is_allowed(opt):
|
||||||
user_options.append('--{0}'.format(opt))
|
user_options.append('--{0}'.format(opt))
|
||||||
elif isinstance(opt, dict):
|
elif isinstance(opt, dict):
|
||||||
keys = list(opt.keys())
|
keys = list(opt.keys())
|
||||||
if len(keys) != 1 or not isinstance(opt[keys[0]], string_types) or not option_is_allowed(keys[0]):
|
if len(keys) != 1 or not isinstance(opt[keys[0]], str) or not option_is_allowed(keys[0]):
|
||||||
error_handler('Error when parsing {0} key-value option {1}: only one key-value is allowed'
|
error_handler('Error when parsing {0} key-value option {1}: only one key-value is allowed'
|
||||||
' and value should be a string'.format(tool, opt[keys[0]]))
|
' and value should be a string'.format(tool, opt[keys[0]]))
|
||||||
user_options.append('--{0}={1}'.format(keys[0], opt[keys[0]]))
|
user_options.append('--{0}={1}'.format(keys[0], opt[keys[0]]))
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from six.moves.urllib_parse import urlparse
|
|
||||||
from threading import Condition, Event, Thread
|
from threading import Condition, Event, Thread
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .connection import Connection
|
from .connection import Connection
|
||||||
from ..dcs import CITUS_COORDINATOR_GROUP_ID
|
from ..dcs import CITUS_COORDINATOR_GROUP_ID
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import socket
|
|||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from six.moves.urllib_parse import urlparse, parse_qsl, unquote
|
from urllib.parse import urlparse, parse_qsl, unquote
|
||||||
|
|
||||||
from .validator import CaseInsensitiveDict, recovery_parameters,\
|
from .validator import CaseInsensitiveDict, recovery_parameters,\
|
||||||
transform_postgresql_parameter_value, transform_recovery_parameter_value
|
transform_postgresql_parameter_value, transform_recovery_parameter_value
|
||||||
@@ -768,9 +768,7 @@ class ConfigHandler(object):
|
|||||||
os.chmod(self._pgpass, stat.S_IWRITE | stat.S_IREAD)
|
os.chmod(self._pgpass, stat.S_IWRITE | stat.S_IREAD)
|
||||||
f.write(line)
|
f.write(line)
|
||||||
|
|
||||||
env = os.environ.copy()
|
return {**os.environ, 'PGPASSFILE': self._pgpass}
|
||||||
env['PGPASSFILE'] = self._pgpass
|
|
||||||
return env
|
|
||||||
|
|
||||||
def write_recovery_conf(self, recovery_params):
|
def write_recovery_conf(self, recovery_params):
|
||||||
self._recovery_params = recovery_params
|
self._recovery_params = recovery_params
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import six
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
@@ -152,7 +151,7 @@ class Rewind(object):
|
|||||||
else: # otherwise analyze pg_controldata output
|
else: # otherwise analyze pg_controldata output
|
||||||
in_recovery, timeline, lsn = self._get_local_timeline_lsn_from_controldata()
|
in_recovery, timeline, lsn = self._get_local_timeline_lsn_from_controldata()
|
||||||
|
|
||||||
log_lsn = format_lsn(lsn) if isinstance(lsn, six.integer_types) else lsn
|
log_lsn = format_lsn(lsn) if isinstance(lsn, int) else lsn
|
||||||
logger.info('Local timeline=%s lsn=%s', timeline, log_lsn)
|
logger.info('Local timeline=%s lsn=%s', timeline, log_lsn)
|
||||||
return in_recovery, timeline, lsn
|
return in_recovery, timeline, lsn
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ class Rewind(object):
|
|||||||
elif primary_timeline > 1:
|
elif primary_timeline > 1:
|
||||||
cur.execute('TIMELINE_HISTORY {0}'.format(primary_timeline))
|
cur.execute('TIMELINE_HISTORY {0}'.format(primary_timeline))
|
||||||
history = cur.fetchone()[1]
|
history = cur.fetchone()[1]
|
||||||
if not isinstance(history, six.string_types):
|
if not isinstance(history, str):
|
||||||
history = bytes(history).decode('utf-8')
|
history = bytes(history).decode('utf-8')
|
||||||
logger.debug('primary: history=%s', history)
|
logger.debug('primary: history=%s', history)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
import six
|
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from urllib3.response import HTTPHeaderDict
|
from urllib3.response import HTTPHeaderDict
|
||||||
@@ -34,8 +33,7 @@ class Bool(namedtuple('Bool', 'version_from,version_till')):
|
|||||||
logger.warning('Removing bool parameter=%s from the config due to the invalid value=%s', name, value)
|
logger.warning('Removing bool parameter=%s from the config due to the invalid value=%s', name, value)
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class Number(abc.ABC, namedtuple('Number', 'version_from,version_till,min_val,max_val,unit')):
|
||||||
class Number(namedtuple('Number', 'version_from,version_till,min_val,max_val,unit')):
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|||||||
+2
-3
@@ -1,8 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import urllib3
|
import urllib3
|
||||||
import six
|
|
||||||
|
|
||||||
from six.moves.urllib_parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
from .utils import USER_AGENT
|
from .utils import USER_AGENT
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ class PatroniRequest(object):
|
|||||||
self._apply_pool_param('ca_certs', cacert)
|
self._apply_pool_param('ca_certs', cacert)
|
||||||
|
|
||||||
def request(self, method, url, body=None, **kwargs):
|
def request(self, method, url, body=None, **kwargs):
|
||||||
if body is not None and not isinstance(body, six.string_types):
|
if body is not None and not isinstance(body, str):
|
||||||
body = json.dumps(body)
|
body = json.dumps(body)
|
||||||
return self._pool.request(method.upper(), url, body=body, **kwargs)
|
return self._pool.request(method.upper(), url, body=body, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -514,21 +514,3 @@ def enable_keepalive(sock, timeout, idle, cnt=3):
|
|||||||
|
|
||||||
for opt in keepalive_socket_options(timeout, idle, cnt):
|
for opt in keepalive_socket_options(timeout, idle, cnt):
|
||||||
sock.setsockopt(*opt)
|
sock.setsockopt(*opt)
|
||||||
|
|
||||||
|
|
||||||
def find_executable(executable, path=None):
|
|
||||||
_, ext = os.path.splitext(executable)
|
|
||||||
|
|
||||||
if (sys.platform == 'win32') and (ext == ''):
|
|
||||||
executable = executable + '.exe' # Set default WIN extension
|
|
||||||
|
|
||||||
if os.path.isfile(executable):
|
|
||||||
return executable
|
|
||||||
|
|
||||||
if path is None:
|
|
||||||
path = os.environ.get('PATH', os.defpath)
|
|
||||||
|
|
||||||
for p in path.split(os.pathsep):
|
|
||||||
f = os.path.join(p, executable)
|
|
||||||
if os.path.isfile(f):
|
|
||||||
return f
|
|
||||||
|
|||||||
+15
-16
@@ -1,12 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from six import string_types
|
from .utils import split_host_port, data_directory_is_empty
|
||||||
|
|
||||||
from .utils import find_executable, split_host_port, data_directory_is_empty
|
|
||||||
from .dcs import dcs_modules
|
from .dcs import dcs_modules
|
||||||
from .exceptions import ConfigParseError
|
from .exceptions import ConfigParseError
|
||||||
|
|
||||||
@@ -173,7 +172,7 @@ class Directory(object):
|
|||||||
yield Result(False, "'{}' does not contain '{}'".format(name, path))
|
yield Result(False, "'{}' does not contain '{}'".format(name, path))
|
||||||
if self.contains_executable:
|
if self.contains_executable:
|
||||||
for program in self.contains_executable:
|
for program in self.contains_executable:
|
||||||
if not find_executable(program, name):
|
if not shutil.which(program, path=name):
|
||||||
yield Result(False, "'{}' does not contain '{}'".format(name, program))
|
yield Result(False, "'{}' does not contain '{}'".format(name, program))
|
||||||
|
|
||||||
|
|
||||||
@@ -190,12 +189,12 @@ class Schema(object):
|
|||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
if isinstance(self.validator, string_types):
|
if isinstance(self.validator, str):
|
||||||
yield Result(isinstance(self.data, string_types), "is not a string", level=1, data=self.data)
|
yield Result(isinstance(self.data, str), "is not a string", level=1, data=self.data)
|
||||||
elif issubclass(type(self.validator), type):
|
elif issubclass(type(self.validator), type):
|
||||||
validator = self.validator
|
validator = self.validator
|
||||||
if self.validator == str:
|
if self.validator == str:
|
||||||
validator = string_types
|
validator = str
|
||||||
yield Result(isinstance(self.data, validator),
|
yield Result(isinstance(self.data, validator),
|
||||||
"is not {}".format(_get_type_name(self.validator)), level=1, data=self.data)
|
"is not {}".format(_get_type_name(self.validator)), level=1, data=self.data)
|
||||||
elif callable(self.validator):
|
elif callable(self.validator):
|
||||||
@@ -290,8 +289,8 @@ class Schema(object):
|
|||||||
|
|
||||||
|
|
||||||
def _get_type_name(python_type):
|
def _get_type_name(python_type):
|
||||||
return {str: 'a string', int: 'and integer', float: 'a number', bool: 'a boolean',
|
return {str: 'a string', int: 'and integer', float: 'a number',
|
||||||
list: 'an array', dict: 'a dictionary', string_types: "a string"}.get(
|
bool: 'a boolean', list: 'an array', dict: 'a dictionary'}.get(
|
||||||
python_type, getattr(python_type, __name__, "unknown type"))
|
python_type, getattr(python_type, __name__, "unknown type"))
|
||||||
|
|
||||||
|
|
||||||
@@ -302,11 +301,11 @@ def assert_(condition, message="Wrong value"):
|
|||||||
userattributes = {"username": "", Optional("password"): ""}
|
userattributes = {"username": "", Optional("password"): ""}
|
||||||
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
|
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
|
||||||
validate_host_port_list.expected_type = list
|
validate_host_port_list.expected_type = list
|
||||||
comma_separated_host_port.expected_type = string_types
|
comma_separated_host_port.expected_type = str
|
||||||
validate_connect_address.expected_type = string_types
|
validate_connect_address.expected_type = str
|
||||||
validate_host_port_listen.expected_type = string_types
|
validate_host_port_listen.expected_type = str
|
||||||
validate_host_port_listen_multiple_hosts.expected_type = string_types
|
validate_host_port_listen_multiple_hosts.expected_type = str
|
||||||
validate_data_dir.expected_type = string_types
|
validate_data_dir.expected_type = str
|
||||||
validate_etcd = {
|
validate_etcd = {
|
||||||
Or("host", "hosts", "srv", "srv_suffix", "url", "proxy"): Case({
|
Or("host", "hosts", "srv", "srv_suffix", "url", "proxy"): Case({
|
||||||
"host": validate_host_port,
|
"host": validate_host_port,
|
||||||
@@ -385,7 +384,7 @@ schema = Schema({
|
|||||||
Optional("bin_dir"): Directory(contains_executable=["pg_ctl", "initdb", "pg_controldata", "pg_basebackup",
|
Optional("bin_dir"): Directory(contains_executable=["pg_ctl", "initdb", "pg_controldata", "pg_basebackup",
|
||||||
"postgres", "pg_isready"]),
|
"postgres", "pg_isready"]),
|
||||||
Optional("parameters"): {
|
Optional("parameters"): {
|
||||||
Optional("unix_socket_directories"): lambda s: assert_(all([isinstance(s, string_types), len(s)]))
|
Optional("unix_socket_directories"): lambda s: assert_(all([isinstance(s, str), len(s)]))
|
||||||
},
|
},
|
||||||
Optional("pg_hba"): [str],
|
Optional("pg_hba"): [str],
|
||||||
Optional("pg_ident"): [str],
|
Optional("pg_ident"): [str],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
|
||||||
@@ -235,8 +234,7 @@ class Watchdog(object):
|
|||||||
return self.config.timing_slack >= 0 and self.impl.is_healthy
|
return self.config.timing_slack >= 0 and self.impl.is_healthy
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class WatchdogBase(abc.ABC):
|
||||||
class WatchdogBase(object):
|
|
||||||
"""A watchdog object when opened requires periodic calls to keepalive.
|
"""A watchdog object when opened requires periodic calls to keepalive.
|
||||||
When keepalive is not called within a timeout the system will be terminated."""
|
When keepalive is not called within a timeout the system will be terminated."""
|
||||||
is_null = False
|
is_null = False
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
urllib3>=1.19.1,!=1.21
|
urllib3>=1.19.1,!=1.21
|
||||||
ipaddress; python_version=="2.7"
|
|
||||||
boto3
|
boto3
|
||||||
PyYAML
|
PyYAML
|
||||||
six >= 1.7
|
|
||||||
kazoo>=1.3.1
|
kazoo>=1.3.1
|
||||||
python-etcd>=0.4.3,<0.5
|
python-etcd>=0.4.3,<0.5
|
||||||
python-consul>=0.7.1
|
python-consul>=0.7.1
|
||||||
|
|||||||
+9
-7
@@ -5,14 +5,16 @@ import socket
|
|||||||
|
|
||||||
import patroni.psycopg as psycopg
|
import patroni.psycopg as psycopg
|
||||||
|
|
||||||
|
from http.server import HTTPServer
|
||||||
|
from io import BytesIO as IO
|
||||||
from mock import Mock, PropertyMock, patch
|
from mock import Mock, PropertyMock, patch
|
||||||
|
from socketserver import ThreadingMixIn
|
||||||
|
|
||||||
from patroni.api import RestApiHandler, RestApiServer
|
from patroni.api import RestApiHandler, RestApiServer
|
||||||
from patroni.dcs import ClusterConfig, Member
|
from patroni.dcs import ClusterConfig, Member
|
||||||
from patroni.ha import _MemberStatus
|
from patroni.ha import _MemberStatus
|
||||||
from patroni.utils import tzutc
|
from patroni.utils import tzutc
|
||||||
from six import BytesIO as IO
|
|
||||||
from six.moves import BaseHTTPServer
|
|
||||||
from six.moves.socketserver import ThreadingMixIn
|
|
||||||
from . import psycopg_connect, MockCursor
|
from . import psycopg_connect, MockCursor
|
||||||
from .test_ha import get_cluster_initialized_without_leader
|
from .test_ha import get_cluster_initialized_without_leader
|
||||||
|
|
||||||
@@ -175,7 +177,7 @@ class MockRestApiServer(RestApiServer):
|
|||||||
|
|
||||||
@patch('ssl.SSLContext.load_cert_chain', Mock())
|
@patch('ssl.SSLContext.load_cert_chain', Mock())
|
||||||
@patch('ssl.SSLContext.wrap_socket', Mock(return_value=0))
|
@patch('ssl.SSLContext.wrap_socket', Mock(return_value=0))
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
@patch.object(HTTPServer, '__init__', Mock())
|
||||||
class TestRestApiHandler(unittest.TestCase):
|
class TestRestApiHandler(unittest.TestCase):
|
||||||
|
|
||||||
_authorization = '\nAuthorization: Basic dGVzdDp0ZXN0'
|
_authorization = '\nAuthorization: Basic dGVzdDp0ZXN0'
|
||||||
@@ -587,14 +589,14 @@ class TestRestApiServer(unittest.TestCase):
|
|||||||
@patch('ssl.SSLContext.load_cert_chain', Mock())
|
@patch('ssl.SSLContext.load_cert_chain', Mock())
|
||||||
@patch('ssl.SSLContext.set_ciphers', Mock())
|
@patch('ssl.SSLContext.set_ciphers', Mock())
|
||||||
@patch('ssl.SSLContext.wrap_socket', Mock(return_value=0))
|
@patch('ssl.SSLContext.wrap_socket', Mock(return_value=0))
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
@patch.object(HTTPServer, '__init__', Mock())
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.srv = MockRestApiServer(Mock(), '', {'listen': '*:8008', 'certfile': 'a', 'verify_client': 'required',
|
self.srv = MockRestApiServer(Mock(), '', {'listen': '*:8008', 'certfile': 'a', 'verify_client': 'required',
|
||||||
'ciphers': '!SSLv1:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1',
|
'ciphers': '!SSLv1:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1',
|
||||||
'allowlist': ['127.0.0.1', '::1/128', '::1/zxc'],
|
'allowlist': ['127.0.0.1', '::1/128', '::1/zxc'],
|
||||||
'allowlist_include_members': True})
|
'allowlist_include_members': True})
|
||||||
|
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
@patch.object(HTTPServer, '__init__', Mock())
|
||||||
def test_reload_config(self):
|
def test_reload_config(self):
|
||||||
bad_config = {'listen': 'foo'}
|
bad_config = {'listen': 'foo'}
|
||||||
self.assertRaises(ValueError, MockRestApiServer, None, '', bad_config)
|
self.assertRaises(ValueError, MockRestApiServer, None, '', bad_config)
|
||||||
@@ -622,7 +624,7 @@ class TestRestApiServer(unittest.TestCase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.assertIsNone(MockRestApiServer.handle_error(None, ('127.0.0.1', 55555)))
|
self.assertIsNone(MockRestApiServer.handle_error(None, ('127.0.0.1', 55555)))
|
||||||
|
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock(side_effect=socket.error))
|
@patch.object(HTTPServer, '__init__', Mock(side_effect=socket.error))
|
||||||
def test_socket_error(self):
|
def test_socket_error(self):
|
||||||
self.assertRaises(socket.error, MockRestApiServer, Mock(), '', {'listen': '*:8008'})
|
self.assertRaises(socket.error, MockRestApiServer, Mock(), '', {'listen': '*:8008'})
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import io
|
|||||||
|
|
||||||
from mock import MagicMock, Mock, patch
|
from mock import MagicMock, Mock, patch
|
||||||
from patroni.config import Config, ConfigParseError
|
from patroni.config import Config, ConfigParseError
|
||||||
from six.moves import builtins
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfig(unittest.TestCase):
|
class TestConfig(unittest.TestCase):
|
||||||
|
|
||||||
@patch('os.path.isfile', Mock(return_value=True))
|
@patch('os.path.isfile', Mock(return_value=True))
|
||||||
@patch('json.load', Mock(side_effect=Exception))
|
@patch('json.load', Mock(side_effect=Exception))
|
||||||
@patch.object(builtins, 'open', MagicMock())
|
@patch('builtins.open', MagicMock())
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
sys.argv = ['patroni.py']
|
sys.argv = ['patroni.py']
|
||||||
os.environ[Config.PATRONI_CONFIG_VARIABLE] = 'restapi: {}\npostgresql: {data_dir: foo}'
|
os.environ[Config.PATRONI_CONFIG_VARIABLE] = 'restapi: {}\npostgresql: {data_dir: foo}'
|
||||||
@@ -137,7 +136,7 @@ class TestConfig(unittest.TestCase):
|
|||||||
new-attr: True
|
new-attr: True
|
||||||
''')
|
''')
|
||||||
|
|
||||||
with patch.object(builtins, 'open', MagicMock(side_effect=open_mock)):
|
with patch('builtins.open', MagicMock(side_effect=open_mock)):
|
||||||
config = Config('postgres0')
|
config = Config('postgres0')
|
||||||
self.assertEqual(config._local_configuration,
|
self.assertEqual(config._local_configuration,
|
||||||
{'test': False, 'test2': {'child-1': 'somestring', 'child-2': 10},
|
{'test': False, 'test2': {'child-1': 'somestring', 'child-2': 10},
|
||||||
|
|||||||
+4
-4
@@ -559,8 +559,8 @@ class TestCtl(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('sys.stdout.isatty', return_value=False)
|
@patch('sys.stdout.isatty', return_value=False)
|
||||||
@patch('patroni.ctl.markup_to_pager')
|
@patch('patroni.ctl.markup_to_pager')
|
||||||
@patch('patroni.ctl.find_executable', return_value=None)
|
@patch('shutil.which', return_value=None)
|
||||||
def test_show_diff(self, mock_find_executable, mock_markup_to_pager, mock_isatty):
|
def test_show_diff(self, mock_which, mock_markup_to_pager, mock_isatty):
|
||||||
show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n")
|
show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n")
|
||||||
mock_markup_to_pager.assert_not_called()
|
mock_markup_to_pager.assert_not_called()
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ class TestCtl(unittest.TestCase):
|
|||||||
show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n")
|
show_diff("foo:\n bar: 1\n", "foo:\n bar: 2\n")
|
||||||
|
|
||||||
# Test that unicode handling doesn't fail with an exception
|
# Test that unicode handling doesn't fail with an exception
|
||||||
mock_find_executable.return_value = '/usr/bin/less'
|
mock_which.return_value = '/usr/bin/less'
|
||||||
show_diff(b"foo:\n bar: \xc3\xb6\xc3\xb6\n".decode('utf-8'),
|
show_diff(b"foo:\n bar: \xc3\xb6\xc3\xb6\n".decode('utf-8'),
|
||||||
b"foo:\n bar: \xc3\xbc\xc3\xbc\n".decode('utf-8'))
|
b"foo:\n bar: \xc3\xbc\xc3\xbc\n".decode('utf-8'))
|
||||||
|
|
||||||
@@ -579,7 +579,7 @@ class TestCtl(unittest.TestCase):
|
|||||||
def test_invoke_editor(self, mock_subprocess_call):
|
def test_invoke_editor(self, mock_subprocess_call):
|
||||||
os.environ.pop('EDITOR', None)
|
os.environ.pop('EDITOR', None)
|
||||||
for e in ('', '/bin/vi'):
|
for e in ('', '/bin/vi'):
|
||||||
with patch('patroni.ctl.find_executable', Mock(return_value=e)):
|
with patch('shutil.which', Mock(return_value=e)):
|
||||||
self.assertRaises(PatroniCtlException, invoke_editor, 'foo: bar\n', 'test')
|
self.assertRaises(PatroniCtlException, invoke_editor, 'foo: bar\n', 'test')
|
||||||
|
|
||||||
@patch('patroni.ctl.get_dcs')
|
@patch('patroni.ctl.get_dcs')
|
||||||
|
|||||||
+5
-6
@@ -18,7 +18,6 @@ from patroni.postgresql.rewind import Rewind
|
|||||||
from patroni.postgresql.slots import SlotsHandler
|
from patroni.postgresql.slots import SlotsHandler
|
||||||
from patroni.utils import tzutc
|
from patroni.utils import tzutc
|
||||||
from patroni.watchdog import Watchdog
|
from patroni.watchdog import Watchdog
|
||||||
from six.moves import builtins
|
|
||||||
|
|
||||||
from . import PostgresInit, MockPostmaster, psycopg_connect, requests_get
|
from . import PostgresInit, MockPostmaster, psycopg_connect, requests_get
|
||||||
from .test_etcd import socket_getaddrinfo, etcd_read, etcd_write
|
from .test_etcd import socket_getaddrinfo, etcd_read, etcd_write
|
||||||
@@ -942,7 +941,7 @@ class TestHa(PostgresInit):
|
|||||||
self.assertEqual(self.ha.run_cycle(), 'PAUSE: waiting to become primary after promote...')
|
self.assertEqual(self.ha.run_cycle(), 'PAUSE: waiting to become primary after promote...')
|
||||||
|
|
||||||
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
||||||
@patch.object(builtins, 'open', mock_open(read_data='1\t0/40159C0\tno recovery target specified\n'))
|
@patch('builtins.open', mock_open(read_data='1\t0/40159C0\tno recovery target specified\n'))
|
||||||
def test_process_healthy_standby_cluster_as_standby_leader(self):
|
def test_process_healthy_standby_cluster_as_standby_leader(self):
|
||||||
self.p.is_leader = false
|
self.p.is_leader = false
|
||||||
self.p.name = 'leader'
|
self.p.name = 'leader'
|
||||||
@@ -1274,7 +1273,7 @@ class TestHa(PostgresInit):
|
|||||||
self.assertEqual(self.ha.get_effective_tags(), {'foo': 'bar'})
|
self.assertEqual(self.ha.get_effective_tags(), {'foo': 'bar'})
|
||||||
|
|
||||||
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
||||||
@patch.object(builtins, 'open', Mock(side_effect=Exception))
|
@patch('builtins.open', Mock(side_effect=Exception))
|
||||||
def test_restore_cluster_config(self):
|
def test_restore_cluster_config(self):
|
||||||
self.ha.cluster.config.data.clear()
|
self.ha.cluster.config.data.clear()
|
||||||
self.ha.has_lock = true
|
self.ha.has_lock = true
|
||||||
@@ -1327,8 +1326,8 @@ class TestHa(PostgresInit):
|
|||||||
"data directory is not accessible: [Errno 5] Input/output error: '{}'".format(self.p.data_dir))
|
"data directory is not accessible: [Errno 5] Input/output error: '{}'".format(self.p.data_dir))
|
||||||
|
|
||||||
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
@patch('patroni.postgresql.mtime', Mock(return_value=1588316884))
|
||||||
@patch.object(builtins, 'open', mock_open(read_data=('1\t0/40159C0\tno recovery target specified\n\n'
|
@patch('builtins.open', mock_open(read_data=('1\t0/40159C0\tno recovery target specified\n\n'
|
||||||
'2\t1/40159C0\tno recovery target specified\n')))
|
'2\t1/40159C0\tno recovery target specified\n')))
|
||||||
def test_update_cluster_history(self):
|
def test_update_cluster_history(self):
|
||||||
self.ha.has_lock = true
|
self.ha.has_lock = true
|
||||||
self.ha.cluster.is_unlocked = false
|
self.ha.cluster.is_unlocked = false
|
||||||
@@ -1392,7 +1391,7 @@ class TestHa(PostgresInit):
|
|||||||
@patch('os.close', Mock())
|
@patch('os.close', Mock())
|
||||||
@patch('os.rename', Mock())
|
@patch('os.rename', Mock())
|
||||||
@patch('patroni.postgresql.Postgresql.is_starting', Mock(return_value=False))
|
@patch('patroni.postgresql.Postgresql.is_starting', Mock(return_value=False))
|
||||||
@patch.object(builtins, 'open', mock_open())
|
@patch('builtins.open', mock_open())
|
||||||
@patch.object(ConfigHandler, 'check_recovery_conf', Mock(return_value=(False, False)))
|
@patch.object(ConfigHandler, 'check_recovery_conf', Mock(return_value=(False, False)))
|
||||||
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=130000))
|
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=130000))
|
||||||
@patch.object(SlotsHandler, 'sync_replication_slots', Mock(return_value=['ls']))
|
@patch.object(SlotsHandler, 'sync_replication_slots', Mock(return_value=['ls']))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from mock import Mock, PropertyMock, mock_open, patch
|
|||||||
from patroni.dcs.kubernetes import Cluster, k8s_client, k8s_config, K8sConfig, K8sConnectionFailed,\
|
from patroni.dcs.kubernetes import Cluster, k8s_client, k8s_config, K8sConfig, K8sConnectionFailed,\
|
||||||
K8sException, K8sObject, Kubernetes, KubernetesError, KubernetesRetriableException,\
|
K8sException, K8sObject, Kubernetes, KubernetesError, KubernetesRetriableException,\
|
||||||
Retry, RetryFailedError, SERVICE_HOST_ENV_NAME, SERVICE_PORT_ENV_NAME
|
Retry, RetryFailedError, SERVICE_HOST_ENV_NAME, SERVICE_PORT_ENV_NAME
|
||||||
from six.moves import builtins
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from . import MockResponse, SleepException
|
from . import MockResponse, SleepException
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ class TestK8sConfig(unittest.TestCase):
|
|||||||
|
|
||||||
with patch('os.environ', {SERVICE_HOST_ENV_NAME: 'a', SERVICE_PORT_ENV_NAME: '1'}),\
|
with patch('os.environ', {SERVICE_HOST_ENV_NAME: 'a', SERVICE_PORT_ENV_NAME: '1'}),\
|
||||||
patch('os.path.isfile', Mock(side_effect=[False, True, True, False, True, True, True, True])),\
|
patch('os.path.isfile', Mock(side_effect=[False, True, True, False, True, True, True, True])),\
|
||||||
patch.object(builtins, 'open', Mock(side_effect=[
|
patch('builtins.open', Mock(side_effect=[
|
||||||
mock_open()(), mock_open(read_data='a')(), mock_open(read_data='a')(),
|
mock_open()(), mock_open(read_data='a')(), mock_open(read_data='a')(),
|
||||||
mock_open()(), mock_open(read_data='a')(), mock_open(read_data='a')()])):
|
mock_open()(), mock_open(read_data='a')(), mock_open(read_data='a')()])):
|
||||||
for _ in range(0, 4):
|
for _ in range(0, 4):
|
||||||
@@ -97,7 +96,7 @@ class TestK8sConfig(unittest.TestCase):
|
|||||||
def test_refresh_token(self):
|
def test_refresh_token(self):
|
||||||
with patch('os.environ', {SERVICE_HOST_ENV_NAME: 'a', SERVICE_PORT_ENV_NAME: '1'}),\
|
with patch('os.environ', {SERVICE_HOST_ENV_NAME: 'a', SERVICE_PORT_ENV_NAME: '1'}),\
|
||||||
patch('os.path.isfile', Mock(side_effect=[True, True, False, True, True, True])),\
|
patch('os.path.isfile', Mock(side_effect=[True, True, False, True, True, True])),\
|
||||||
patch.object(builtins, 'open', Mock(side_effect=[
|
patch('builtins.open', Mock(side_effect=[
|
||||||
mock_open(read_data='cert')(), mock_open(read_data='a')(),
|
mock_open(read_data='cert')(), mock_open(read_data='a')(),
|
||||||
mock_open()(), mock_open(read_data='b')(), mock_open(read_data='c')()])):
|
mock_open()(), mock_open(read_data='b')(), mock_open(read_data='c')()])):
|
||||||
k8s_config.load_incluster_config(token_refresh_interval=datetime.timedelta(milliseconds=100))
|
k8s_config.load_incluster_config(token_refresh_interval=datetime.timedelta(milliseconds=100))
|
||||||
@@ -122,20 +121,20 @@ class TestK8sConfig(unittest.TestCase):
|
|||||||
"clusters": [{"name": "local", "cluster": {"server": "https://a:1/", "certificate-authority": "a"}}],
|
"clusters": [{"name": "local", "cluster": {"server": "https://a:1/", "certificate-authority": "a"}}],
|
||||||
"users": [{"name": "local", "user": {"username": "a", "password": "b", "client-certificate": "c"}}]
|
"users": [{"name": "local", "user": {"username": "a", "password": "b", "client-certificate": "c"}}]
|
||||||
}
|
}
|
||||||
with patch.object(builtins, 'open', mock_open(read_data=json.dumps(config))):
|
with patch('builtins.open', mock_open(read_data=json.dumps(config))):
|
||||||
k8s_config.load_kube_config()
|
k8s_config.load_kube_config()
|
||||||
self.assertEqual(k8s_config.server, 'https://a:1')
|
self.assertEqual(k8s_config.server, 'https://a:1')
|
||||||
self.assertEqual(k8s_config.pool_config, {'ca_certs': 'a', 'cert_file': 'c', 'cert_reqs': 'CERT_REQUIRED',
|
self.assertEqual(k8s_config.pool_config, {'ca_certs': 'a', 'cert_file': 'c', 'cert_reqs': 'CERT_REQUIRED',
|
||||||
'maxsize': 10, 'num_pools': 10})
|
'maxsize': 10, 'num_pools': 10})
|
||||||
|
|
||||||
config["users"][0]["user"]["token"] = "token"
|
config["users"][0]["user"]["token"] = "token"
|
||||||
with patch.object(builtins, 'open', mock_open(read_data=json.dumps(config))):
|
with patch('builtins.open', mock_open(read_data=json.dumps(config))):
|
||||||
k8s_config.load_kube_config()
|
k8s_config.load_kube_config()
|
||||||
self.assertEqual(k8s_config.headers.get('authorization'), 'Bearer token')
|
self.assertEqual(k8s_config.headers.get('authorization'), 'Bearer token')
|
||||||
|
|
||||||
config["users"][0]["user"]["client-key-data"] = base64.b64encode(b'foobar').decode('utf-8')
|
config["users"][0]["user"]["client-key-data"] = base64.b64encode(b'foobar').decode('utf-8')
|
||||||
config["clusters"][0]["cluster"]["certificate-authority-data"] = base64.b64encode(b'foobar').decode('utf-8')
|
config["clusters"][0]["cluster"]["certificate-authority-data"] = base64.b64encode(b'foobar').decode('utf-8')
|
||||||
with patch.object(builtins, 'open', mock_open(read_data=json.dumps(config))),\
|
with patch('builtins.open', mock_open(read_data=json.dumps(config))),\
|
||||||
patch('os.write', Mock()), patch('os.close', Mock()),\
|
patch('os.write', Mock()), patch('os.close', Mock()),\
|
||||||
patch('os.remove') as mock_remove,\
|
patch('os.remove') as mock_remove,\
|
||||||
patch('atexit.register') as mock_atexit,\
|
patch('atexit.register') as mock_atexit,\
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import yaml
|
|||||||
from mock import Mock, patch
|
from mock import Mock, patch
|
||||||
from patroni.config import Config
|
from patroni.config import Config
|
||||||
from patroni.log import PatroniLogger
|
from patroni.log import PatroniLogger
|
||||||
from six.moves.queue import Queue, Full
|
from queue import Queue, Full
|
||||||
|
|
||||||
_LOG = logging.getLogger(__name__)
|
_LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import time
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import patroni.config as config
|
import patroni.config as config
|
||||||
|
from http.server import HTTPServer
|
||||||
from mock import Mock, PropertyMock, patch
|
from mock import Mock, PropertyMock, patch
|
||||||
from patroni.api import RestApiServer
|
from patroni.api import RestApiServer
|
||||||
from patroni.async_executor import AsyncExecutor
|
from patroni.async_executor import AsyncExecutor
|
||||||
@@ -15,7 +16,6 @@ from patroni.postgresql import Postgresql
|
|||||||
from patroni.postgresql.config import ConfigHandler
|
from patroni.postgresql.config import ConfigHandler
|
||||||
from patroni import check_psycopg
|
from patroni import check_psycopg
|
||||||
from patroni.__main__ import Patroni, main as _main, patroni_main
|
from patroni.__main__ import Patroni, main as _main, patroni_main
|
||||||
from six.moves import BaseHTTPServer, builtins
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from . import psycopg_connect, SleepException
|
from . import psycopg_connect, SleepException
|
||||||
@@ -44,7 +44,7 @@ class MockFrozenImporter(object):
|
|||||||
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())
|
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())
|
||||||
@patch.object(Postgresql, 'is_running', Mock(return_value=MockPostmaster()))
|
@patch.object(Postgresql, 'is_running', Mock(return_value=MockPostmaster()))
|
||||||
@patch.object(Postgresql, 'call_nowait', Mock())
|
@patch.object(Postgresql, 'call_nowait', Mock())
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
@patch.object(HTTPServer, '__init__', Mock())
|
||||||
@patch.object(AsyncExecutor, 'run', Mock())
|
@patch.object(AsyncExecutor, 'run', Mock())
|
||||||
@patch.object(etcd.Client, 'write', etcd_write)
|
@patch.object(etcd.Client, 'write', etcd_write)
|
||||||
@patch.object(etcd.Client, 'read', etcd_read)
|
@patch.object(etcd.Client, 'read', etcd_read)
|
||||||
@@ -63,7 +63,7 @@ class TestPatroni(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('pkgutil.iter_importers', Mock(return_value=[MockFrozenImporter()]))
|
@patch('pkgutil.iter_importers', Mock(return_value=[MockFrozenImporter()]))
|
||||||
@patch('sys.frozen', Mock(return_value=True), create=True)
|
@patch('sys.frozen', Mock(return_value=True), create=True)
|
||||||
@patch.object(BaseHTTPServer.HTTPServer, '__init__', Mock())
|
@patch.object(HTTPServer, '__init__', Mock())
|
||||||
@patch.object(etcd.Client, 'read', etcd_read)
|
@patch.object(etcd.Client, 'read', etcd_read)
|
||||||
@patch.object(Thread, 'start', Mock())
|
@patch.object(Thread, 'start', Mock())
|
||||||
@patch.object(AbstractEtcdClientWithFailover, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
@patch.object(AbstractEtcdClientWithFailover, 'machines', PropertyMock(return_value=['http://remotehost:2379']))
|
||||||
@@ -196,7 +196,7 @@ class TestPatroni(unittest.TestCase):
|
|||||||
self.p.shutdown()
|
self.p.shutdown()
|
||||||
|
|
||||||
def test_check_psycopg(self):
|
def test_check_psycopg(self):
|
||||||
with patch.object(builtins, '__import__', Mock(side_effect=ImportError)):
|
with patch('builtins.__import__', Mock(side_effect=ImportError)):
|
||||||
self.assertRaises(SystemExit, check_psycopg)
|
self.assertRaises(SystemExit, check_psycopg)
|
||||||
with patch.object(builtins, '__import__', mock_import):
|
with patch('builtins.__import__', mock_import):
|
||||||
self.assertRaises(SystemExit, check_psycopg)
|
self.assertRaises(SystemExit, check_psycopg)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from patroni.postgresql.bootstrap import Bootstrap
|
|||||||
from patroni.postgresql.callback_executor import CallbackAction
|
from patroni.postgresql.callback_executor import CallbackAction
|
||||||
from patroni.postgresql.postmaster import PostmasterProcess
|
from patroni.postgresql.postmaster import PostmasterProcess
|
||||||
from patroni.utils import RetryFailedError
|
from patroni.utils import RetryFailedError
|
||||||
from six.moves import builtins
|
|
||||||
from threading import Thread, current_thread
|
from threading import Thread, current_thread
|
||||||
|
|
||||||
from . import BaseTestPostgresql, MockCursor, MockPostmaster, psycopg_connect
|
from . import BaseTestPostgresql, MockCursor, MockPostmaster, psycopg_connect
|
||||||
@@ -231,7 +230,7 @@ class TestPostgresql(BaseTestPostgresql):
|
|||||||
self.assertEqual(self.p.state, 'restart failed (restarting)')
|
self.assertEqual(self.p.state, 'restart failed (restarting)')
|
||||||
|
|
||||||
@patch('os.chmod', Mock())
|
@patch('os.chmod', Mock())
|
||||||
@patch.object(builtins, 'open', MagicMock())
|
@patch('builtins.open', MagicMock())
|
||||||
def test_write_pgpass(self):
|
def test_write_pgpass(self):
|
||||||
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
|
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
|
||||||
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
|
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
|
||||||
@@ -312,11 +311,11 @@ class TestPostgresql(BaseTestPostgresql):
|
|||||||
|
|
||||||
mock_read_auto = mock_open(read_data=read_data)
|
mock_read_auto = mock_open(read_data=read_data)
|
||||||
mock_read_auto.return_value.__iter__ = lambda o: iter(o.readline, '')
|
mock_read_auto.return_value.__iter__ = lambda o: iter(o.readline, '')
|
||||||
with patch.object(builtins, 'open', Mock(side_effect=[mock_open()(), mock_read_auto(), IOError])),\
|
with patch('builtins.open', Mock(side_effect=[mock_open()(), mock_read_auto(), IOError])),\
|
||||||
patch('os.chmod', Mock()):
|
patch('os.chmod', Mock()):
|
||||||
self.p.config.write_postgresql_conf()
|
self.p.config.write_postgresql_conf()
|
||||||
|
|
||||||
with patch.object(builtins, 'open', Mock(side_effect=[mock_open()(), IOError])), patch('os.chmod', Mock()):
|
with patch('builtins.open', Mock(side_effect=[mock_open()(), IOError])), patch('os.chmod', Mock()):
|
||||||
self.p.config.write_postgresql_conf()
|
self.p.config.write_postgresql_conf()
|
||||||
self.p.config.write_recovery_conf({'foo': 'bar'})
|
self.p.config.write_recovery_conf({'foo': 'bar'})
|
||||||
self.p.config.write_postgresql_conf()
|
self.p.config.write_postgresql_conf()
|
||||||
@@ -552,9 +551,9 @@ class TestPostgresql(BaseTestPostgresql):
|
|||||||
|
|
||||||
@patch.object(Postgresql, '_version_file_exists', Mock(return_value=True))
|
@patch.object(Postgresql, '_version_file_exists', Mock(return_value=True))
|
||||||
def test_get_major_version(self):
|
def test_get_major_version(self):
|
||||||
with patch.object(builtins, 'open', mock_open(read_data='9.4')):
|
with patch('builtins.open', mock_open(read_data='9.4')):
|
||||||
self.assertEqual(self.p.get_major_version(), 90400)
|
self.assertEqual(self.p.get_major_version(), 90400)
|
||||||
with patch.object(builtins, 'open', Mock(side_effect=Exception)):
|
with patch('builtins.open', Mock(side_effect=Exception)):
|
||||||
self.assertEqual(self.p.get_major_version(), 0)
|
self.assertEqual(self.p.get_major_version(), 0)
|
||||||
|
|
||||||
def test_postmaster_start_time(self):
|
def test_postmaster_start_time(self):
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import unittest
|
|||||||
|
|
||||||
from mock import Mock, patch, mock_open
|
from mock import Mock, patch, mock_open
|
||||||
from patroni.postgresql.postmaster import PostmasterProcess
|
from patroni.postgresql.postmaster import PostmasterProcess
|
||||||
from six.moves import builtins
|
|
||||||
|
|
||||||
|
|
||||||
class MockProcess(object):
|
class MockProcess(object):
|
||||||
@@ -169,7 +168,7 @@ class TestPostmasterProcess(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('psutil.Process.__init__', Mock(side_effect=psutil.NoSuchProcess(123)))
|
@patch('psutil.Process.__init__', Mock(side_effect=psutil.NoSuchProcess(123)))
|
||||||
def test_read_postmaster_pidfile(self):
|
def test_read_postmaster_pidfile(self):
|
||||||
with patch.object(builtins, 'open', Mock(side_effect=IOError)):
|
with patch('builtins.open', Mock(side_effect=IOError)):
|
||||||
self.assertIsNone(PostmasterProcess.from_pidfile(''))
|
self.assertIsNone(PostmasterProcess.from_pidfile(''))
|
||||||
with patch.object(builtins, 'open', mock_open(read_data='123\n')):
|
with patch('builtins.open', mock_open(read_data='123\n')):
|
||||||
self.assertIsNone(PostmasterProcess.from_pidfile(''))
|
self.assertIsNone(PostmasterProcess.from_pidfile(''))
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from mock import Mock, PropertyMock, patch, mock_open
|
|||||||
from patroni.postgresql import Postgresql
|
from patroni.postgresql import Postgresql
|
||||||
from patroni.postgresql.cancellable import CancellableSubprocess
|
from patroni.postgresql.cancellable import CancellableSubprocess
|
||||||
from patroni.postgresql.rewind import Rewind
|
from patroni.postgresql.rewind import Rewind
|
||||||
from six.moves import builtins
|
|
||||||
|
|
||||||
from . import BaseTestPostgresql, MockCursor, psycopg_connect
|
from . import BaseTestPostgresql, MockCursor, psycopg_connect
|
||||||
|
|
||||||
@@ -193,7 +192,7 @@ class TestRewind(BaseTestPostgresql):
|
|||||||
m = mock_open(read_data='/usr/lib/postgres/9.6/bin/postgres "-D" "data/postgresql0" \
|
m = mock_open(read_data='/usr/lib/postgres/9.6/bin/postgres "-D" "data/postgresql0" \
|
||||||
"--listen_addresses=127.0.0.1" "--port=5432" "--hot_standby=on" "--wal_level=hot_standby" \
|
"--listen_addresses=127.0.0.1" "--port=5432" "--hot_standby=on" "--wal_level=hot_standby" \
|
||||||
"--wal_log_hints=on" "--max_wal_senders=5" "--max_replication_slots=5"\n')
|
"--wal_log_hints=on" "--max_wal_senders=5" "--max_replication_slots=5"\n')
|
||||||
with patch.object(builtins, 'open', m):
|
with patch('builtins.open', m):
|
||||||
data = self.r.read_postmaster_opts()
|
data = self.r.read_postmaster_opts()
|
||||||
self.assertEqual(data['wal_level'], 'hot_standby')
|
self.assertEqual(data['wal_level'], 'hot_standby')
|
||||||
self.assertEqual(int(data['max_replication_slots']), 5)
|
self.assertEqual(int(data['max_replication_slots']), 5)
|
||||||
|
|||||||
+1
-10
@@ -2,7 +2,7 @@ import unittest
|
|||||||
|
|
||||||
from mock import Mock, patch
|
from mock import Mock, patch
|
||||||
from patroni.exceptions import PatroniException
|
from patroni.exceptions import PatroniException
|
||||||
from patroni.utils import Retry, RetryFailedError, enable_keepalive, find_executable, polling_loop, validate_directory
|
from patroni.utils import Retry, RetryFailedError, enable_keepalive, polling_loop, validate_directory
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
@@ -41,15 +41,6 @@ class TestUtils(unittest.TestCase):
|
|||||||
with patch('sys.platform', platform):
|
with patch('sys.platform', platform):
|
||||||
self.assertIsNone(enable_keepalive(Mock(), 10, 5))
|
self.assertIsNone(enable_keepalive(Mock(), 10, 5))
|
||||||
|
|
||||||
@patch('sys.platform', 'win32')
|
|
||||||
def test_find_executable(self):
|
|
||||||
with patch('os.path.isfile', Mock(return_value=True)):
|
|
||||||
self.assertEqual(find_executable('vim'), 'vim.exe')
|
|
||||||
with patch('os.path.isfile', Mock(return_value=False)):
|
|
||||||
self.assertIsNone(find_executable('vim'))
|
|
||||||
with patch('os.path.isfile', Mock(side_effect=[False, True])):
|
|
||||||
self.assertEqual(find_executable('vim', '/'), '/vim.exe')
|
|
||||||
|
|
||||||
|
|
||||||
@patch('time.sleep', Mock())
|
@patch('time.sleep', Mock())
|
||||||
class TestRetrySleeper(unittest.TestCase):
|
class TestRetrySleeper(unittest.TestCase):
|
||||||
|
|||||||
+15
-9
@@ -4,10 +4,10 @@ import socket
|
|||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from io import StringIO
|
||||||
from mock import Mock, patch, mock_open
|
from mock import Mock, patch, mock_open
|
||||||
from patroni.dcs import dcs_modules
|
from patroni.dcs import dcs_modules
|
||||||
from patroni.validator import schema
|
from patroni.validator import schema
|
||||||
from six import StringIO
|
|
||||||
|
|
||||||
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
|
available_dcs = [m.split(".")[-1] for m in dcs_modules()]
|
||||||
config = {
|
config = {
|
||||||
@@ -94,14 +94,18 @@ config = {
|
|||||||
|
|
||||||
directories = []
|
directories = []
|
||||||
files = []
|
files = []
|
||||||
|
binaries = []
|
||||||
|
|
||||||
|
|
||||||
def isfile_side_effect(arg):
|
def isfile_side_effect(arg):
|
||||||
if arg.endswith('.exe'):
|
|
||||||
arg = arg[:-4]
|
|
||||||
return arg in files
|
return arg in files
|
||||||
|
|
||||||
|
|
||||||
|
def which_side_effect(arg, path=None):
|
||||||
|
binary = arg if path is None else os.path.join(path, arg)
|
||||||
|
return arg if binary in binaries else None
|
||||||
|
|
||||||
|
|
||||||
def isdir_side_effect(arg):
|
def isdir_side_effect(arg):
|
||||||
return arg in directories
|
return arg in directories
|
||||||
|
|
||||||
@@ -134,6 +138,7 @@ def parse_output(output):
|
|||||||
@patch('os.path.exists', Mock(side_effect=exists_side_effect))
|
@patch('os.path.exists', Mock(side_effect=exists_side_effect))
|
||||||
@patch('os.path.isdir', Mock(side_effect=isdir_side_effect))
|
@patch('os.path.isdir', Mock(side_effect=isdir_side_effect))
|
||||||
@patch('os.path.isfile', Mock(side_effect=isfile_side_effect))
|
@patch('os.path.isfile', Mock(side_effect=isfile_side_effect))
|
||||||
|
@patch('shutil.which', Mock(side_effect=which_side_effect))
|
||||||
@patch('sys.stderr', new_callable=StringIO)
|
@patch('sys.stderr', new_callable=StringIO)
|
||||||
@patch('sys.stdout', new_callable=StringIO)
|
@patch('sys.stdout', new_callable=StringIO)
|
||||||
class TestValidator(unittest.TestCase):
|
class TestValidator(unittest.TestCase):
|
||||||
@@ -141,6 +146,7 @@ class TestValidator(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
del files[:]
|
del files[:]
|
||||||
del directories[:]
|
del directories[:]
|
||||||
|
del binaries[:]
|
||||||
|
|
||||||
def test_empty_config(self, mock_out, mock_err):
|
def test_empty_config(self, mock_out, mock_err):
|
||||||
errors = schema({})
|
errors = schema({})
|
||||||
@@ -191,12 +197,12 @@ class TestValidator(unittest.TestCase):
|
|||||||
directories.append(os.path.join(config["postgresql"]["data_dir"], "pg_wal"))
|
directories.append(os.path.join(config["postgresql"]["data_dir"], "pg_wal"))
|
||||||
files.append(os.path.join(config["postgresql"]["data_dir"], "global", "pg_control"))
|
files.append(os.path.join(config["postgresql"]["data_dir"], "global", "pg_control"))
|
||||||
files.append(os.path.join(config["postgresql"]["data_dir"], "PG_VERSION"))
|
files.append(os.path.join(config["postgresql"]["data_dir"], "PG_VERSION"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "pg_ctl"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "pg_ctl"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "initdb"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "initdb"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "pg_controldata"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "pg_controldata"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "pg_basebackup"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "pg_basebackup"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "postgres"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "postgres"))
|
||||||
files.append(os.path.join(config["postgresql"]["bin_dir"], "pg_isready"))
|
binaries.append(os.path.join(config["postgresql"]["bin_dir"], "pg_isready"))
|
||||||
with patch('patroni.validator.open', mock_open(read_data='12')):
|
with patch('patroni.validator.open', mock_open(read_data='12')):
|
||||||
errors = schema(config)
|
errors = schema(config)
|
||||||
output = "\n".join(errors)
|
output = "\n".join(errors)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import patroni.psycopg as psycopg
|
|||||||
from mock import Mock, PropertyMock, patch, mock_open
|
from mock import Mock, PropertyMock, patch, mock_open
|
||||||
from patroni.scripts import wale_restore
|
from patroni.scripts import wale_restore
|
||||||
from patroni.scripts.wale_restore import WALERestore, main as _main, get_major_version
|
from patroni.scripts.wale_restore import WALERestore, main as _main, get_major_version
|
||||||
from six.moves import builtins
|
|
||||||
from threading import current_thread
|
from threading import current_thread
|
||||||
|
|
||||||
from . import MockConnect, psycopg_connect
|
from . import MockConnect, psycopg_connect
|
||||||
@@ -128,9 +127,9 @@ class TestWALERestore(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('os.path.isfile', Mock(return_value=True))
|
@patch('os.path.isfile', Mock(return_value=True))
|
||||||
def test_get_major_version(self):
|
def test_get_major_version(self):
|
||||||
with patch.object(builtins, 'open', mock_open(read_data='9.4')):
|
with patch('builtins.open', mock_open(read_data='9.4')):
|
||||||
self.assertEqual(get_major_version("data"), 9.4)
|
self.assertEqual(get_major_version("data"), 9.4)
|
||||||
with patch.object(builtins, 'open', side_effect=OSError):
|
with patch('builtins.open', side_effect=OSError):
|
||||||
self.assertEqual(get_major_version("data"), 0.0)
|
self.assertEqual(get_major_version("data"), 0.0)
|
||||||
|
|
||||||
@patch('os.path.islink', Mock(return_value=True))
|
@patch('os.path.islink', Mock(return_value=True))
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import select
|
import select
|
||||||
import six
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from kazoo.client import KazooClient, KazooState
|
from kazoo.client import KazooClient, KazooState
|
||||||
@@ -30,7 +29,7 @@ class MockKazooClient(Mock):
|
|||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
def get(self, path, watch=None):
|
def get(self, path, watch=None):
|
||||||
if not isinstance(path, six.string_types):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Invalid type for 'path' (string expected)")
|
raise TypeError("Invalid type for 'path' (string expected)")
|
||||||
if path == '/broken/status':
|
if path == '/broken/status':
|
||||||
return (b'{', ZnodeStat(0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0))
|
return (b'{', ZnodeStat(0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0))
|
||||||
@@ -57,7 +56,7 @@ class MockKazooClient(Mock):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_children(path, watch=None, include_data=False):
|
def get_children(path, watch=None, include_data=False):
|
||||||
if not isinstance(path, six.string_types):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Invalid type for 'path' (string expected)")
|
raise TypeError("Invalid type for 'path' (string expected)")
|
||||||
if path.startswith('/no_node'):
|
if path.startswith('/no_node'):
|
||||||
raise NoNodeError
|
raise NoNodeError
|
||||||
@@ -66,9 +65,9 @@ class MockKazooClient(Mock):
|
|||||||
return ['foo', 'bar', 'buzz']
|
return ['foo', 'bar', 'buzz']
|
||||||
|
|
||||||
def create(self, path, value=b"", acl=None, ephemeral=False, sequence=False, makepath=False):
|
def create(self, path, value=b"", acl=None, ephemeral=False, sequence=False, makepath=False):
|
||||||
if not isinstance(path, six.string_types):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Invalid type for 'path' (string expected)")
|
raise TypeError("Invalid type for 'path' (string expected)")
|
||||||
if not isinstance(value, (six.binary_type,)):
|
if not isinstance(value, bytes):
|
||||||
raise TypeError("Invalid type for 'value' (must be a byte string)")
|
raise TypeError("Invalid type for 'value' (must be a byte string)")
|
||||||
if b'Exception' in value:
|
if b'Exception' in value:
|
||||||
raise Exception
|
raise Exception
|
||||||
@@ -82,9 +81,9 @@ class MockKazooClient(Mock):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set(path, value, version=-1):
|
def set(path, value, version=-1):
|
||||||
if not isinstance(path, six.string_types):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Invalid type for 'path' (string expected)")
|
raise TypeError("Invalid type for 'path' (string expected)")
|
||||||
if not isinstance(value, (six.binary_type,)):
|
if not isinstance(value, bytes):
|
||||||
raise TypeError("Invalid type for 'value' (must be a byte string)")
|
raise TypeError("Invalid type for 'value' (must be a byte string)")
|
||||||
if path == '/service/bla/optime/leader':
|
if path == '/service/bla/optime/leader':
|
||||||
raise Exception
|
raise Exception
|
||||||
@@ -101,7 +100,7 @@ class MockKazooClient(Mock):
|
|||||||
return self.set(path, value, version) or Mock()
|
return self.set(path, value, version) or Mock()
|
||||||
|
|
||||||
def delete(self, path, version=-1, recursive=False):
|
def delete(self, path, version=-1, recursive=False):
|
||||||
if not isinstance(path, six.string_types):
|
if not isinstance(path, str):
|
||||||
raise TypeError("Invalid type for 'path' (string expected)")
|
raise TypeError("Invalid type for 'path' (string expected)")
|
||||||
self.exists = False
|
self.exists = False
|
||||||
if path == '/service/test/leader':
|
if path == '/service/test/leader':
|
||||||
|
|||||||
Reference in New Issue
Block a user