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:
Alexander Kukushkin
2023-03-13 17:00:04 +01:00
committed by GitHub
parent 373affe707
commit c1bfb0e6d6
39 changed files with 138 additions and 220 deletions
+2 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -5
View File
@@ -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))
+2 -2
View File
@@ -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
View File
@@ -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)
-15
View File
@@ -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:
+1 -3
View File
@@ -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():
+5 -7
View File
@@ -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 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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 -6
View File
@@ -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)
+2 -4
View File
@@ -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]]))
+1 -1
View File
@@ -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
+2 -4
View File
@@ -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
+2 -3
View File
@@ -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 -3
View File
@@ -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
View File
@@ -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)
-18
View File
@@ -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
View File
@@ -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 -3
View File
@@ -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
-2
View File
@@ -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
View File
@@ -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'})
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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']))
+5 -6
View File
@@ -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
View File
@@ -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__)
+5 -5
View File
@@ -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)
+5 -6
View File
@@ -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):
+2 -3
View File
@@ -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(''))
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+2 -3
View File
@@ -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))
+7 -8
View File
@@ -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':