You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1487 lines
45 KiB
1487 lines
45 KiB
import os
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import uuid
|
|
import unittest
|
|
from unittest.mock import Mock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from kazoo.testing import KazooTestCase
|
|
from kazoo.exceptions import (
|
|
AuthFailedError,
|
|
BadArgumentsError,
|
|
BadVersionError,
|
|
ConfigurationError,
|
|
ConnectionClosedError,
|
|
ConnectionLoss,
|
|
InvalidACLError,
|
|
NoAuthError,
|
|
NoNodeError,
|
|
NodeExistsError,
|
|
SessionExpiredError,
|
|
KazooException,
|
|
)
|
|
from kazoo.protocol.connection import _CONNECTION_DROP
|
|
from kazoo.protocol.states import KeeperState, KazooState
|
|
from kazoo.tests.util import CI_ZK_VERSION
|
|
|
|
|
|
if sys.version_info > (3,): # pragma: nocover
|
|
|
|
def u(s):
|
|
return s
|
|
|
|
else: # pragma: nocover
|
|
|
|
def u(s):
|
|
return unicode(s, "unicode_escape") # noqa
|
|
|
|
|
|
class TestClientTransitions(KazooTestCase):
|
|
@staticmethod
|
|
def make_event():
|
|
return threading.Event()
|
|
|
|
def test_connection_and_disconnection(self):
|
|
states = []
|
|
rc = threading.Event()
|
|
|
|
@self.client.add_listener
|
|
def listener(state):
|
|
states.append(state)
|
|
if state == KazooState.CONNECTED:
|
|
rc.set()
|
|
|
|
self.client.stop()
|
|
assert states == [KazooState.LOST]
|
|
states.pop()
|
|
|
|
self.client.start()
|
|
rc.wait(2)
|
|
assert states == [KazooState.CONNECTED]
|
|
rc.clear()
|
|
states.pop()
|
|
self.expire_session(self.make_event)
|
|
rc.wait(2)
|
|
|
|
req_states = [KazooState.LOST, KazooState.CONNECTED]
|
|
assert states == req_states
|
|
|
|
|
|
class TestClientConstructor(unittest.TestCase):
|
|
def _makeOne(self, *args, **kw):
|
|
from kazoo.client import KazooClient
|
|
|
|
return KazooClient(*args, **kw)
|
|
|
|
def test_invalid_handler(self):
|
|
from kazoo.handlers.threading import SequentialThreadingHandler
|
|
|
|
with pytest.raises(ConfigurationError):
|
|
self._makeOne(handler=SequentialThreadingHandler)
|
|
|
|
def test_chroot(self):
|
|
assert self._makeOne(hosts="127.0.0.1:2181/").chroot == ""
|
|
assert self._makeOne(hosts="127.0.0.1:2181/a").chroot == "/a"
|
|
assert self._makeOne(hosts="127.0.0.1/a").chroot == "/a"
|
|
assert self._makeOne(hosts="127.0.0.1/a/b").chroot == "/a/b"
|
|
assert (
|
|
self._makeOne(hosts="127.0.0.1:2181,127.0.0.1:2182/a/b").chroot
|
|
== "/a/b"
|
|
)
|
|
|
|
def test_connection_timeout(self):
|
|
from kazoo.handlers.threading import KazooTimeoutError
|
|
|
|
client = self._makeOne(hosts="127.0.0.1:9")
|
|
assert client.handler.timeout_exception is KazooTimeoutError
|
|
|
|
with pytest.raises(KazooTimeoutError):
|
|
client.start(0.1)
|
|
|
|
def test_ordered_host_selection(self):
|
|
client = self._makeOne(
|
|
hosts="127.0.0.1:9,127.0.0.2:9/a", randomize_hosts=False
|
|
)
|
|
hosts = [h for h in client.hosts]
|
|
assert hosts == [("127.0.0.1", 9), ("127.0.0.2", 9)]
|
|
|
|
def test_invalid_hostname(self):
|
|
client = self._makeOne(hosts="nosuchhost/a")
|
|
timeout = client.handler.timeout_exception
|
|
with pytest.raises(timeout):
|
|
client.start(0.1)
|
|
|
|
def test_another_invalid_hostname(self):
|
|
with pytest.raises(ValueError):
|
|
self._makeOne(hosts="/nosuchhost/a")
|
|
|
|
def test_retry_options_dict(self):
|
|
from kazoo.retry import KazooRetry
|
|
|
|
client = self._makeOne(
|
|
command_retry=dict(max_tries=99), connection_retry=dict(delay=99)
|
|
)
|
|
assert type(client._conn_retry) is KazooRetry
|
|
assert type(client._retry) is KazooRetry
|
|
assert client._retry.max_tries == 99
|
|
assert client._conn_retry.delay == 99
|
|
|
|
|
|
class TestAuthentication(KazooTestCase):
|
|
def _makeAuth(self, *args, **kwargs):
|
|
from kazoo.security import make_digest_acl
|
|
|
|
return make_digest_acl(*args, **kwargs)
|
|
|
|
def test_auth(self):
|
|
username = uuid.uuid4().hex
|
|
password = uuid.uuid4().hex
|
|
|
|
digest_auth = "%s:%s" % (username, password)
|
|
acl = self._makeAuth(username, password, all=True)
|
|
|
|
client = self._get_client()
|
|
client.start()
|
|
client.add_auth("digest", digest_auth)
|
|
client.default_acl = (acl,)
|
|
|
|
try:
|
|
client.create("/1")
|
|
client.create("/1/2")
|
|
client.ensure_path("/1/2/3")
|
|
|
|
eve = self._get_client()
|
|
|
|
eve.start()
|
|
|
|
with pytest.raises(NoAuthError):
|
|
eve.get("/1/2")
|
|
|
|
# try again with the wrong auth token
|
|
eve.add_auth("digest", "badbad:bad")
|
|
|
|
with pytest.raises(NoAuthError):
|
|
eve.get("/1/2")
|
|
|
|
finally:
|
|
# Ensure we remove the ACL protected nodes
|
|
client.delete("/1", recursive=True)
|
|
eve.stop()
|
|
eve.close()
|
|
|
|
def test_connect_auth(self):
|
|
username = uuid.uuid4().hex
|
|
password = uuid.uuid4().hex
|
|
|
|
digest_auth = "%s:%s" % (username, password)
|
|
acl = self._makeAuth(username, password, all=True)
|
|
|
|
client = self._get_client(auth_data=[("digest", digest_auth)])
|
|
client.start()
|
|
try:
|
|
client.create("/1", acl=(acl,))
|
|
# give ZK a chance to copy data to other node
|
|
time.sleep(0.1)
|
|
|
|
with pytest.raises(NoAuthError):
|
|
self.client.get("/1")
|
|
|
|
finally:
|
|
client.delete("/1")
|
|
client.stop()
|
|
client.close()
|
|
|
|
def test_unicode_auth(self):
|
|
username = u(r"xe4/\hm")
|
|
password = u(r"/\xe4hm")
|
|
digest_auth = "%s:%s" % (username, password)
|
|
acl = self._makeAuth(username, password, all=True)
|
|
|
|
client = self._get_client()
|
|
client.start()
|
|
client.add_auth("digest", digest_auth)
|
|
client.default_acl = (acl,)
|
|
|
|
try:
|
|
client.create("/1")
|
|
client.ensure_path("/1/2/3")
|
|
|
|
eve = self._get_client()
|
|
eve.start()
|
|
|
|
with pytest.raises(NoAuthError):
|
|
eve.get("/1/2")
|
|
|
|
# try again with the wrong auth token
|
|
eve.add_auth("digest", "badbad:bad")
|
|
|
|
with pytest.raises(NoAuthError):
|
|
eve.get("/1/2")
|
|
|
|
finally:
|
|
# Ensure we remove the ACL protected nodes
|
|
client.delete("/1", recursive=True)
|
|
eve.stop()
|
|
eve.close()
|
|
|
|
def test_invalid_auth(self):
|
|
client = self._get_client()
|
|
client.start()
|
|
|
|
with pytest.raises(TypeError):
|
|
client.add_auth("digest", ("user", "pass"))
|
|
|
|
with pytest.raises(TypeError):
|
|
client.add_auth(None, ("user", "pass"))
|
|
|
|
def test_async_auth(self):
|
|
client = self._get_client()
|
|
client.start()
|
|
username = uuid.uuid4().hex
|
|
password = uuid.uuid4().hex
|
|
digest_auth = "%s:%s" % (username, password)
|
|
result = client.add_auth_async("digest", digest_auth)
|
|
assert result.get() is True
|
|
|
|
def test_async_auth_failure(self):
|
|
client = self._get_client()
|
|
client.start()
|
|
username = uuid.uuid4().hex
|
|
password = uuid.uuid4().hex
|
|
digest_auth = "%s:%s" % (username, password)
|
|
|
|
with pytest.raises(AuthFailedError):
|
|
client.add_auth("unknown-scheme", digest_auth)
|
|
|
|
client.stop()
|
|
|
|
def test_add_auth_on_reconnect(self):
|
|
client = self._get_client()
|
|
client.start()
|
|
client.add_auth("digest", "jsmith:jsmith")
|
|
client._connection._socket.shutdown(socket.SHUT_RDWR)
|
|
while not client.connected:
|
|
time.sleep(0.1)
|
|
assert ("digest", "jsmith:jsmith") in client.auth_data
|
|
|
|
|
|
class TestConnection(KazooTestCase):
|
|
@staticmethod
|
|
def make_event():
|
|
return threading.Event()
|
|
|
|
@staticmethod
|
|
def make_condition():
|
|
return threading.Condition()
|
|
|
|
def test_chroot_warning(self):
|
|
k = self._get_nonchroot_client()
|
|
k.chroot = "abba"
|
|
try:
|
|
with patch("warnings.warn") as mock_func:
|
|
k.start()
|
|
assert mock_func.called
|
|
finally:
|
|
k.stop()
|
|
|
|
def test_session_expire(self):
|
|
from kazoo.protocol.states import KazooState
|
|
|
|
cv = self.make_event()
|
|
|
|
def watch_events(event):
|
|
if event == KazooState.LOST:
|
|
cv.set()
|
|
|
|
self.client.add_listener(watch_events)
|
|
self.expire_session(self.make_event)
|
|
cv.wait(3)
|
|
assert cv.is_set()
|
|
|
|
def test_bad_session_expire(self):
|
|
from kazoo.protocol.states import KazooState
|
|
|
|
cv = self.make_event()
|
|
ab = self.make_event()
|
|
|
|
def watch_events(event):
|
|
if event == KazooState.LOST:
|
|
ab.set()
|
|
raise Exception("oops")
|
|
cv.set()
|
|
|
|
self.client.add_listener(watch_events)
|
|
self.expire_session(self.make_event)
|
|
ab.wait(0.5)
|
|
assert ab.is_set()
|
|
cv.wait(0.5)
|
|
assert not cv.is_set()
|
|
|
|
def test_state_listener(self):
|
|
from kazoo.protocol.states import KazooState
|
|
|
|
states = []
|
|
condition = self.make_condition()
|
|
|
|
def listener(state):
|
|
with condition:
|
|
states.append(state)
|
|
condition.notify_all()
|
|
|
|
self.client.stop()
|
|
assert self.client.state == KazooState.LOST
|
|
self.client.add_listener(listener)
|
|
self.client.start(5)
|
|
|
|
with condition:
|
|
if not states:
|
|
condition.wait(5)
|
|
|
|
assert len(states) == 1
|
|
assert states[0] == KazooState.CONNECTED
|
|
|
|
def test_invalid_listener(self):
|
|
with pytest.raises(ConfigurationError):
|
|
self.client.add_listener(15)
|
|
|
|
def test_listener_only_called_on_real_state_change(self):
|
|
from kazoo.protocol.states import KazooState
|
|
|
|
assert self.client.state == KazooState.CONNECTED
|
|
called = [False]
|
|
condition = self.make_event()
|
|
|
|
def listener(state):
|
|
called[0] = True
|
|
condition.set()
|
|
|
|
self.client.add_listener(listener)
|
|
self.client._make_state_change(KazooState.CONNECTED)
|
|
condition.wait(3)
|
|
assert called[0] is False
|
|
|
|
def test_no_connection(self):
|
|
client = self.client
|
|
client.stop()
|
|
assert client.connected is False
|
|
assert client.client_id is None
|
|
|
|
with pytest.raises(ConnectionClosedError):
|
|
client.exists("/")
|
|
|
|
def test_close_connecting_connection(self):
|
|
client = self.client
|
|
client.stop()
|
|
ev = self.make_event()
|
|
|
|
def close_on_connecting(state):
|
|
if state in (KazooState.CONNECTED, KazooState.LOST):
|
|
ev.set()
|
|
|
|
client.add_listener(close_on_connecting)
|
|
client.start()
|
|
|
|
# Wait until we connect
|
|
ev.wait(5)
|
|
ev.clear()
|
|
self.client._call(_CONNECTION_DROP, client.handler.async_result())
|
|
|
|
client.stop()
|
|
|
|
# ...and then wait until the connection is lost
|
|
ev.wait(5)
|
|
|
|
with pytest.raises(ConnectionClosedError):
|
|
self.client.create("/foobar")
|
|
|
|
def test_double_start(self):
|
|
assert self.client.connected is True
|
|
self.client.start()
|
|
assert self.client.connected is True
|
|
|
|
def test_double_stop(self):
|
|
self.client.stop()
|
|
assert self.client.connected is False
|
|
self.client.stop()
|
|
assert self.client.connected is False
|
|
|
|
def test_restart(self):
|
|
assert self.client.connected is True
|
|
self.client.restart()
|
|
assert self.client.connected is True
|
|
|
|
def test_closed(self):
|
|
client = self.client
|
|
client.stop()
|
|
|
|
write_sock = client._connection._write_sock
|
|
|
|
# close the connection to free the socket
|
|
client.close()
|
|
assert client._connection._write_sock is None
|
|
|
|
# sneak in and patch client to simulate race between a thread
|
|
# calling stop(); close() and one running a command
|
|
oldstate = client._state
|
|
client._state = KeeperState.CONNECTED
|
|
client._connection._write_sock = write_sock
|
|
|
|
try:
|
|
# simulate call made after write socket is closed
|
|
with pytest.raises(ConnectionClosedError):
|
|
client.exists("/")
|
|
|
|
# simulate call made after write socket is set to None
|
|
client._connection._write_sock = None
|
|
|
|
with pytest.raises(ConnectionClosedError):
|
|
client.exists("/")
|
|
|
|
finally:
|
|
# reset for teardown
|
|
client._state = oldstate
|
|
client._connection._write_sock = None
|
|
|
|
def test_watch_trigger_expire(self):
|
|
client = self.client
|
|
cv = self.make_event()
|
|
|
|
client.create("/test", b"")
|
|
|
|
def test_watch(event):
|
|
cv.set()
|
|
|
|
client.get("/test/", watch=test_watch)
|
|
self.expire_session(self.make_event)
|
|
|
|
cv.wait(3)
|
|
assert cv.is_set()
|
|
|
|
|
|
class TestClient(KazooTestCase):
|
|
def _makeOne(self, *args):
|
|
from kazoo.handlers.threading import SequentialThreadingHandler
|
|
|
|
return SequentialThreadingHandler(*args)
|
|
|
|
def _getKazooState(self):
|
|
from kazoo.protocol.states import KazooState
|
|
|
|
return KazooState
|
|
|
|
def test_server_version_retries_fail(self):
|
|
client = self.client
|
|
side_effects = [
|
|
"",
|
|
"zookeeper.version=",
|
|
"zookeeper.version=1.",
|
|
"zookeeper.ver",
|
|
]
|
|
client.command = MagicMock()
|
|
client.command.side_effect = side_effects
|
|
with pytest.raises(KazooException):
|
|
client.server_version(retries=len(side_effects) - 1)
|
|
|
|
def test_server_version_retries_eventually_ok(self):
|
|
client = self.client
|
|
actual_version = "zookeeper.version=1.2"
|
|
side_effects = []
|
|
for i in range(0, len(actual_version) + 1):
|
|
side_effects.append(actual_version[0:i])
|
|
client.command = MagicMock()
|
|
client.command.side_effect = side_effects
|
|
assert client.server_version(retries=len(side_effects) - 1) == (1, 2)
|
|
|
|
def test_client_id(self):
|
|
client_id = self.client.client_id
|
|
assert type(client_id) is tuple
|
|
# make sure password is of correct length
|
|
assert len(client_id[1]) == 16
|
|
|
|
def test_connected(self):
|
|
client = self.client
|
|
assert client.connected
|
|
|
|
def test_create(self):
|
|
client = self.client
|
|
path = client.create("/1")
|
|
assert path == "/1"
|
|
assert client.exists("/1")
|
|
|
|
def test_create_on_broken_connection(self):
|
|
client = self.client
|
|
client.start()
|
|
|
|
client._state = KeeperState.EXPIRED_SESSION
|
|
with pytest.raises(SessionExpiredError):
|
|
client.create("/closedpath", b"bar")
|
|
|
|
client._state = KeeperState.AUTH_FAILED
|
|
with pytest.raises(AuthFailedError):
|
|
client.create("/closedpath", b"bar")
|
|
|
|
client.stop()
|
|
client.close()
|
|
|
|
with pytest.raises(ConnectionClosedError):
|
|
client.create("/closedpath", b"bar")
|
|
|
|
def test_create_null_data(self):
|
|
client = self.client
|
|
client.create("/nulldata", None)
|
|
value, _ = client.get("/nulldata")
|
|
assert value is None
|
|
|
|
def test_create_empty_string(self):
|
|
client = self.client
|
|
client.create("/empty", b"")
|
|
value, _ = client.get("/empty")
|
|
assert value == b""
|
|
|
|
def test_create_unicode_path(self):
|
|
client = self.client
|
|
path = client.create(u("/ascii"))
|
|
assert path == u("/ascii")
|
|
path = client.create(u("/\xe4hm"))
|
|
assert path == u("/\xe4hm")
|
|
|
|
def test_create_async_returns_unchrooted_path(self):
|
|
client = self.client
|
|
path = client.create_async("/1").get()
|
|
assert path == "/1"
|
|
|
|
def test_create_invalid_path(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.create(("a",))
|
|
with pytest.raises(ValueError):
|
|
client.create(".")
|
|
with pytest.raises(ValueError):
|
|
client.create("/a/../b")
|
|
with pytest.raises(BadArgumentsError):
|
|
client.create("/b\x00")
|
|
with pytest.raises(BadArgumentsError):
|
|
client.create("/b\x1e")
|
|
|
|
def test_create_invalid_arguments(self):
|
|
from kazoo.security import OPEN_ACL_UNSAFE
|
|
|
|
single_acl = OPEN_ACL_UNSAFE[0]
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.create("a", acl="all")
|
|
with pytest.raises(TypeError):
|
|
client.create("a", acl=single_acl)
|
|
with pytest.raises(TypeError):
|
|
client.create("a", value=["a"])
|
|
with pytest.raises(TypeError):
|
|
client.create("a", ephemeral="yes")
|
|
with pytest.raises(TypeError):
|
|
client.create("a", sequence="yes")
|
|
with pytest.raises(TypeError):
|
|
client.create("a", makepath="yes")
|
|
|
|
def test_create_value(self):
|
|
client = self.client
|
|
client.create("/1", b"bytes")
|
|
data, stat = client.get("/1")
|
|
assert data == b"bytes"
|
|
|
|
def test_create_unicode_value(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.create("/1", u("\xe4hm"))
|
|
|
|
def test_create_large_value(self):
|
|
client = self.client
|
|
kb_512 = b"a" * (512 * 1024)
|
|
client.create("/1", kb_512)
|
|
assert client.exists("/1")
|
|
mb_2 = b"a" * (2 * 1024 * 1024)
|
|
with pytest.raises(ConnectionLoss):
|
|
client.create("/2", mb_2)
|
|
|
|
def test_create_acl_duplicate(self):
|
|
from kazoo.security import OPEN_ACL_UNSAFE
|
|
|
|
single_acl = OPEN_ACL_UNSAFE[0]
|
|
client = self.client
|
|
client.create("/1", acl=[single_acl, single_acl])
|
|
acls, stat = client.get_acls("/1")
|
|
# ZK >3.4 removes duplicate ACL entries
|
|
if CI_ZK_VERSION:
|
|
version = CI_ZK_VERSION
|
|
else:
|
|
version = client.server_version()
|
|
assert len(acls) == 1 if version > (3, 4) else 2
|
|
|
|
def test_create_acl_empty_list(self):
|
|
from kazoo.security import OPEN_ACL_UNSAFE
|
|
|
|
client = self.client
|
|
client.create("/1", acl=[])
|
|
acls, stat = client.get_acls("/1")
|
|
assert acls == OPEN_ACL_UNSAFE
|
|
|
|
def test_version_no_connection(self):
|
|
self.client.stop()
|
|
with pytest.raises(ConnectionLoss):
|
|
self.client.server_version()
|
|
|
|
def test_create_ephemeral(self):
|
|
client = self.client
|
|
client.create("/1", b"ephemeral", ephemeral=True)
|
|
data, stat = client.get("/1")
|
|
assert data == b"ephemeral"
|
|
assert stat.ephemeralOwner == client.client_id[0]
|
|
|
|
def test_create_no_ephemeral(self):
|
|
client = self.client
|
|
client.create("/1", b"val1")
|
|
data, stat = client.get("/1")
|
|
assert not stat.ephemeralOwner
|
|
|
|
def test_create_ephemeral_no_children(self):
|
|
from kazoo.exceptions import NoChildrenForEphemeralsError
|
|
|
|
client = self.client
|
|
client.create("/1", b"ephemeral", ephemeral=True)
|
|
with pytest.raises(NoChildrenForEphemeralsError):
|
|
client.create("/1/2", b"val1")
|
|
with pytest.raises(NoChildrenForEphemeralsError):
|
|
client.create("/1/2", b"val1", ephemeral=True)
|
|
|
|
def test_create_sequence(self):
|
|
client = self.client
|
|
client.create("/folder")
|
|
path = client.create("/folder/a", b"sequence", sequence=True)
|
|
assert path == "/folder/a0000000000"
|
|
path2 = client.create("/folder/a", b"sequence", sequence=True)
|
|
assert path2 == "/folder/a0000000001"
|
|
path3 = client.create("/folder/", b"sequence", sequence=True)
|
|
assert path3 == "/folder/0000000002"
|
|
|
|
def test_create_ephemeral_sequence(self):
|
|
basepath = "/" + uuid.uuid4().hex
|
|
realpath = self.client.create(
|
|
basepath, b"sandwich", sequence=True, ephemeral=True
|
|
)
|
|
assert basepath != realpath and realpath.startswith(basepath)
|
|
data, stat = self.client.get(realpath)
|
|
assert data == b"sandwich"
|
|
|
|
def test_create_makepath(self):
|
|
self.client.create("/1/2", b"val1", makepath=True)
|
|
data, stat = self.client.get("/1/2")
|
|
assert data == b"val1"
|
|
|
|
self.client.create("/1/2/3/4/5", b"val2", makepath=True)
|
|
data, stat = self.client.get("/1/2/3/4/5")
|
|
assert data == b"val2"
|
|
|
|
with pytest.raises(NodeExistsError):
|
|
self.client.create("/1/2/3/4/5", b"val2", makepath=True)
|
|
|
|
def test_create_makepath_incompatible_acls(self):
|
|
from kazoo.client import KazooClient
|
|
from kazoo.security import make_digest_acl_credential, CREATOR_ALL_ACL
|
|
|
|
credential = make_digest_acl_credential("username", "password")
|
|
alt_client = KazooClient(
|
|
self.cluster[0].address + self.client.chroot,
|
|
max_retries=5,
|
|
auth_data=[("digest", credential)],
|
|
handler=self._makeOne(),
|
|
)
|
|
alt_client.start()
|
|
alt_client.create("/1/2", b"val2", makepath=True, acl=CREATOR_ALL_ACL)
|
|
|
|
try:
|
|
with pytest.raises(NoAuthError):
|
|
self.client.create("/1/2/3/4/5", b"val2", makepath=True)
|
|
|
|
finally:
|
|
alt_client.delete("/", recursive=True)
|
|
alt_client.stop()
|
|
|
|
def test_create_no_makepath(self):
|
|
with pytest.raises(NoNodeError):
|
|
self.client.create("/1/2", b"val1")
|
|
with pytest.raises(NoNodeError):
|
|
self.client.create("/1/2", b"val1", makepath=False)
|
|
|
|
self.client.create("/1/2", b"val1", makepath=True)
|
|
with pytest.raises(NoNodeError):
|
|
self.client.create("/1/2/3/4", b"val1", makepath=False)
|
|
|
|
def test_create_exists(self):
|
|
from kazoo.exceptions import NodeExistsError
|
|
|
|
client = self.client
|
|
path = client.create("/1")
|
|
with pytest.raises(NodeExistsError):
|
|
client.create(path)
|
|
|
|
def test_create_stat(self):
|
|
if CI_ZK_VERSION:
|
|
version = CI_ZK_VERSION
|
|
else:
|
|
version = self.client.server_version()
|
|
if not version or version < (3, 5):
|
|
pytest.skip("Must use Zookeeper 3.5 or above")
|
|
client = self.client
|
|
path, stat1 = client.create("/1", b"bytes", include_data=True)
|
|
data, stat2 = client.get("/1")
|
|
assert data == b"bytes"
|
|
assert stat1 == stat2
|
|
|
|
def test_create_get_set(self):
|
|
nodepath = "/" + uuid.uuid4().hex
|
|
|
|
self.client.create(nodepath, b"sandwich", ephemeral=True)
|
|
|
|
data, stat = self.client.get(nodepath)
|
|
assert data == b"sandwich"
|
|
|
|
newstat = self.client.set(nodepath, b"hats", stat.version)
|
|
assert newstat
|
|
assert newstat.version > stat.version
|
|
|
|
# Some other checks of the ZnodeStat object we got
|
|
assert newstat.acl_version == stat.acl_version
|
|
assert newstat.created == stat.ctime / 1000.0
|
|
assert newstat.last_modified == newstat.mtime / 1000.0
|
|
assert newstat.owner_session_id == stat.ephemeralOwner
|
|
assert newstat.creation_transaction_id == stat.czxid
|
|
assert newstat.last_modified_transaction_id == newstat.mzxid
|
|
assert newstat.data_length == newstat.dataLength
|
|
assert newstat.children_count == stat.numChildren
|
|
assert newstat.children_version == stat.cversion
|
|
|
|
def test_get_invalid_arguments(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.get(("a", "b"))
|
|
with pytest.raises(TypeError):
|
|
client.get("a", watch=True)
|
|
|
|
def test_bad_argument(self):
|
|
client = self.client
|
|
client.ensure_path("/1")
|
|
with pytest.raises(TypeError):
|
|
self.client.set("/1", 1)
|
|
|
|
def test_ensure_path(self):
|
|
client = self.client
|
|
client.ensure_path("/1/2")
|
|
assert client.exists("/1/2")
|
|
|
|
client.ensure_path("/1/2/3/4")
|
|
assert client.exists("/1/2/3/4")
|
|
|
|
def test_sync(self):
|
|
client = self.client
|
|
assert client.sync("/") == "/"
|
|
# Albeit surprising, you can sync anything, even what does not exist.
|
|
assert client.sync("/not_there") == "/not_there"
|
|
|
|
def test_exists(self):
|
|
nodepath = "/" + uuid.uuid4().hex
|
|
|
|
exists = self.client.exists(nodepath)
|
|
assert exists is None
|
|
|
|
self.client.create(nodepath, b"sandwich", ephemeral=True)
|
|
exists = self.client.exists(nodepath)
|
|
assert exists
|
|
assert isinstance(exists.version, int)
|
|
|
|
multi_node_nonexistent = "/" + uuid.uuid4().hex + "/hats"
|
|
exists = self.client.exists(multi_node_nonexistent)
|
|
assert exists is None
|
|
|
|
def test_exists_invalid_arguments(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.exists(("a", "b"))
|
|
with pytest.raises(TypeError):
|
|
client.exists("a", watch=True)
|
|
|
|
def test_exists_watch(self):
|
|
nodepath = "/" + uuid.uuid4().hex
|
|
event = self.client.handler.event_object()
|
|
|
|
def w(watch_event):
|
|
assert watch_event.path == nodepath
|
|
event.set()
|
|
|
|
exists = self.client.exists(nodepath, watch=w)
|
|
assert exists is None
|
|
|
|
self.client.create(nodepath, ephemeral=True)
|
|
|
|
event.wait(1)
|
|
assert event.is_set() is True
|
|
|
|
def test_exists_watcher_exception(self):
|
|
nodepath = "/" + uuid.uuid4().hex
|
|
event = self.client.handler.event_object()
|
|
|
|
# if the watcher throws an exception, all we can really do is log it
|
|
def w(watch_event):
|
|
assert watch_event.path == nodepath
|
|
event.set()
|
|
|
|
raise Exception("test exception in callback")
|
|
|
|
exists = self.client.exists(nodepath, watch=w)
|
|
assert exists is None
|
|
|
|
self.client.create(nodepath, ephemeral=True)
|
|
|
|
event.wait(1)
|
|
assert event.is_set() is True
|
|
|
|
def test_create_delete(self):
|
|
nodepath = "/" + uuid.uuid4().hex
|
|
|
|
self.client.create(nodepath, b"zzz")
|
|
|
|
self.client.delete(nodepath)
|
|
|
|
exists = self.client.exists(nodepath)
|
|
assert exists is None
|
|
|
|
def test_get_acls(self):
|
|
from kazoo.security import make_digest_acl
|
|
|
|
user = "user"
|
|
passw = "pass"
|
|
acl = make_digest_acl(user, passw, all=True)
|
|
client = self.client
|
|
try:
|
|
client.create("/a", acl=[acl])
|
|
client.add_auth("digest", "{}:{}".format(user, passw))
|
|
assert acl in client.get_acls("/a")[0]
|
|
finally:
|
|
client.delete("/a")
|
|
|
|
def test_get_acls_invalid_arguments(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.get_acls(("a", "b"))
|
|
|
|
def test_set_acls(self):
|
|
from kazoo.security import make_digest_acl
|
|
|
|
user = "user"
|
|
passw = "pass"
|
|
acl = make_digest_acl(user, passw, all=True)
|
|
client = self.client
|
|
client.create("/a")
|
|
try:
|
|
client.set_acls("/a", [acl])
|
|
client.add_auth("digest", "{}:{}".format(user, passw))
|
|
assert acl in client.get_acls("/a")[0]
|
|
finally:
|
|
client.delete("/a")
|
|
|
|
def test_set_acls_empty(self):
|
|
client = self.client
|
|
client.create("/a")
|
|
with pytest.raises(InvalidACLError):
|
|
client.set_acls("/a", [])
|
|
|
|
def test_set_acls_no_node(self):
|
|
from kazoo.security import OPEN_ACL_UNSAFE
|
|
|
|
client = self.client
|
|
with pytest.raises(NoNodeError):
|
|
client.set_acls("/a", OPEN_ACL_UNSAFE)
|
|
|
|
def test_set_acls_invalid_arguments(self):
|
|
from kazoo.security import OPEN_ACL_UNSAFE
|
|
|
|
single_acl = OPEN_ACL_UNSAFE[0]
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.set_acls(("a", "b"), ())
|
|
with pytest.raises(TypeError):
|
|
client.set_acls("a", single_acl)
|
|
with pytest.raises(TypeError):
|
|
client.set_acls("a", "all")
|
|
with pytest.raises(TypeError):
|
|
client.set_acls("a", [single_acl], "V1")
|
|
|
|
def test_set(self):
|
|
client = self.client
|
|
client.create("a", b"first")
|
|
stat = client.set("a", b"second")
|
|
data, stat2 = client.get("a")
|
|
assert data == b"second"
|
|
assert stat == stat2
|
|
|
|
def test_set_null_data(self):
|
|
client = self.client
|
|
client.create("/nulldata", b"not none")
|
|
client.set("/nulldata", None)
|
|
value, _ = client.get("/nulldata")
|
|
assert value is None
|
|
|
|
def test_set_empty_string(self):
|
|
client = self.client
|
|
client.create("/empty", b"not empty")
|
|
client.set("/empty", b"")
|
|
value, _ = client.get("/empty")
|
|
assert value == b""
|
|
|
|
def test_set_invalid_arguments(self):
|
|
client = self.client
|
|
client.create("a", b"first")
|
|
with pytest.raises(TypeError):
|
|
client.set(("a", "b"), b"value")
|
|
with pytest.raises(TypeError):
|
|
client.set("a", ["v", "w"])
|
|
with pytest.raises(TypeError):
|
|
client.set("a", b"value", "V1")
|
|
|
|
def test_delete(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b")
|
|
assert "b" in client.get_children("a")
|
|
client.delete("/a/b")
|
|
assert "b" not in client.get_children("a")
|
|
|
|
def test_delete_recursive(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b/c")
|
|
client.ensure_path("/a/b/d")
|
|
client.delete("/a/b", recursive=True)
|
|
client.delete("/a/b/c", recursive=True)
|
|
assert "b" not in client.get_children("a")
|
|
|
|
def test_delete_invalid_arguments(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b")
|
|
with pytest.raises(TypeError):
|
|
client.delete("/a/b", recursive="all")
|
|
with pytest.raises(TypeError):
|
|
client.delete(("a", "b"))
|
|
with pytest.raises(TypeError):
|
|
client.delete("/a/b", version="V1")
|
|
|
|
def test_get_children(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b/c")
|
|
client.ensure_path("/a/b/d")
|
|
assert client.get_children("/a") == ["b"]
|
|
assert set(client.get_children("/a/b")) == set(["c", "d"])
|
|
assert client.get_children("/a/b/c") == []
|
|
|
|
def test_get_children2(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b")
|
|
children, stat = client.get_children("/a", include_data=True)
|
|
value, stat2 = client.get("/a")
|
|
assert children == ["b"]
|
|
assert stat2.version == stat.version
|
|
|
|
def test_get_children2_many_nodes(self):
|
|
client = self.client
|
|
client.ensure_path("/a/b")
|
|
client.ensure_path("/a/c")
|
|
client.ensure_path("/a/d")
|
|
children, stat = client.get_children("/a", include_data=True)
|
|
value, stat2 = client.get("/a")
|
|
assert set(children) == set(["b", "c", "d"])
|
|
assert stat2.version == stat.version
|
|
|
|
def test_get_children_no_node(self):
|
|
client = self.client
|
|
with pytest.raises(NoNodeError):
|
|
client.get_children("/none")
|
|
with pytest.raises(NoNodeError):
|
|
client.get_children("/none", include_data=True)
|
|
|
|
def test_get_children_invalid_path(self):
|
|
client = self.client
|
|
with pytest.raises(ValueError):
|
|
client.get_children("../a")
|
|
|
|
def test_get_children_invalid_arguments(self):
|
|
client = self.client
|
|
with pytest.raises(TypeError):
|
|
client.get_children(("a", "b"))
|
|
with pytest.raises(TypeError):
|
|
client.get_children("a", watch=True)
|
|
with pytest.raises(TypeError):
|
|
client.get_children("a", include_data="yes")
|
|
|
|
def test_invalid_auth(self):
|
|
from kazoo.exceptions import AuthFailedError
|
|
from kazoo.protocol.states import KeeperState
|
|
|
|
client = self.client
|
|
client.stop()
|
|
client._state = KeeperState.AUTH_FAILED
|
|
|
|
with pytest.raises(AuthFailedError):
|
|
client.get("/")
|
|
|
|
def test_client_state(self):
|
|
from kazoo.protocol.states import KeeperState
|
|
|
|
assert self.client.client_state == KeeperState.CONNECTED
|
|
|
|
def test_update_host_list(self):
|
|
from kazoo.client import KazooClient
|
|
from kazoo.protocol.states import KeeperState
|
|
|
|
hosts = self.cluster[0].address
|
|
# create a client with only one server in its list
|
|
client = KazooClient(hosts=hosts)
|
|
client.start()
|
|
|
|
# try to change the chroot, not currently allowed
|
|
with pytest.raises(ConfigurationError):
|
|
client.set_hosts(hosts + "/new_chroot")
|
|
|
|
# grow the cluster to 3
|
|
client.set_hosts(self.servers)
|
|
|
|
# shut down the first host
|
|
try:
|
|
self.cluster[0].stop()
|
|
time.sleep(5)
|
|
assert client.client_state == KeeperState.CONNECTED
|
|
finally:
|
|
self.cluster[0].run()
|
|
|
|
# utility for test_request_queuing*
|
|
def _make_request_queuing_client(self):
|
|
from kazoo.client import KazooClient
|
|
|
|
server = self.cluster[0]
|
|
handler = self._makeOne()
|
|
# create a client with only one server in its list, and
|
|
# infinite retries
|
|
client = KazooClient(
|
|
hosts=server.address + self.client.chroot,
|
|
handler=handler,
|
|
connection_retry=dict(
|
|
max_tries=-1,
|
|
delay=0.1,
|
|
backoff=1,
|
|
max_jitter=0.0,
|
|
sleep_func=handler.sleep_func,
|
|
),
|
|
)
|
|
|
|
return client, server
|
|
|
|
# utility for test_request_queuing*
|
|
def _request_queuing_common(self, client, server, path, expire_session):
|
|
ev_suspended = client.handler.event_object()
|
|
ev_connected = client.handler.event_object()
|
|
|
|
def listener(state):
|
|
if state == KazooState.SUSPENDED:
|
|
ev_suspended.set()
|
|
elif state == KazooState.CONNECTED:
|
|
ev_connected.set()
|
|
|
|
client.add_listener(listener)
|
|
|
|
# wait for the client to connect
|
|
client.start()
|
|
|
|
try:
|
|
# force the client to suspend
|
|
server.stop()
|
|
|
|
ev_suspended.wait(5)
|
|
assert ev_suspended.is_set()
|
|
ev_connected.clear()
|
|
|
|
# submit a request, expecting it to be queued
|
|
result = client.create_async(path)
|
|
assert len(client._queue) != 0
|
|
assert result.ready() is False
|
|
assert client.state == KazooState.SUSPENDED
|
|
|
|
# optionally cause a SessionExpiredError to occur by
|
|
# mangling the first byte of the session password.
|
|
if expire_session:
|
|
b0 = b"\x00"
|
|
if client._session_passwd[0] == 0:
|
|
b0 = b"\xff"
|
|
client._session_passwd = b0 + client._session_passwd[1:]
|
|
finally:
|
|
server.run()
|
|
|
|
# wait for the client to reconnect (either with a recovered
|
|
# session, or with a new one if expire_session was set)
|
|
ev_connected.wait(5)
|
|
assert ev_connected.is_set()
|
|
|
|
return result
|
|
|
|
def test_request_queuing_session_recovered(self):
|
|
path = "/" + uuid.uuid4().hex
|
|
client, server = self._make_request_queuing_client()
|
|
|
|
try:
|
|
result = self._request_queuing_common(
|
|
client=client, server=server, path=path, expire_session=False
|
|
)
|
|
|
|
assert result.get() == path
|
|
assert client.exists(path) is not None
|
|
finally:
|
|
client.stop()
|
|
|
|
def test_request_queuing_session_expired(self):
|
|
path = "/" + uuid.uuid4().hex
|
|
client, server = self._make_request_queuing_client()
|
|
|
|
try:
|
|
result = self._request_queuing_common(
|
|
client=client, server=server, path=path, expire_session=True
|
|
)
|
|
|
|
assert len(client._queue) == 0
|
|
with pytest.raises(SessionExpiredError):
|
|
result.get()
|
|
finally:
|
|
client.stop()
|
|
|
|
|
|
class TestSSLClient(KazooTestCase):
|
|
def setUp(self):
|
|
if CI_ZK_VERSION and CI_ZK_VERSION < (3, 5):
|
|
pytest.skip("Must use Zookeeper 3.5 or above")
|
|
ssl_path = tempfile.mkdtemp()
|
|
key_path = os.path.join(ssl_path, "key.pem")
|
|
cert_path = os.path.join(ssl_path, "cert.pem")
|
|
cacert_path = os.path.join(ssl_path, "cacert.pem")
|
|
with open(key_path, "wb") as key_file:
|
|
key_file.write(
|
|
self.cluster.get_ssl_client_configuration()["client_key"]
|
|
)
|
|
with open(cert_path, "wb") as cert_file:
|
|
cert_file.write(
|
|
self.cluster.get_ssl_client_configuration()["client_cert"]
|
|
)
|
|
with open(cacert_path, "wb") as cacert_file:
|
|
cacert_file.write(
|
|
self.cluster.get_ssl_client_configuration()["ca_cert"]
|
|
)
|
|
self.setup_zookeeper(
|
|
use_ssl=True, keyfile=key_path, certfile=cert_path, ca=cacert_path
|
|
)
|
|
|
|
def test_create(self):
|
|
client = self.client
|
|
path = client.create("/1")
|
|
assert path == "/1"
|
|
assert client.exists("/1")
|
|
|
|
|
|
dummy_dict = {
|
|
"aversion": 1,
|
|
"ctime": 0,
|
|
"cversion": 1,
|
|
"czxid": 110,
|
|
"dataLength": 1,
|
|
"ephemeralOwner": "ben",
|
|
"mtime": 1,
|
|
"mzxid": 1,
|
|
"numChildren": 0,
|
|
"pzxid": 1,
|
|
"version": 1,
|
|
}
|
|
|
|
|
|
class TestClientTransactions(KazooTestCase):
|
|
def setUp(self):
|
|
KazooTestCase.setUp(self)
|
|
skip = False
|
|
if CI_ZK_VERSION and CI_ZK_VERSION < (3, 4):
|
|
skip = True
|
|
elif CI_ZK_VERSION and CI_ZK_VERSION >= (3, 4):
|
|
skip = False
|
|
else:
|
|
ver = self.client.server_version()
|
|
if ver[1] < 4:
|
|
skip = True
|
|
if skip:
|
|
pytest.skip("Must use Zookeeper 3.4 or above")
|
|
|
|
def test_basic_create(self):
|
|
t = self.client.transaction()
|
|
t.create("/freddy")
|
|
t.create("/fred", ephemeral=True)
|
|
t.create("/smith", sequence=True)
|
|
results = t.commit()
|
|
assert len(results) == 3
|
|
assert results[0] == "/freddy"
|
|
assert results[2].startswith("/smith0") is True
|
|
|
|
def test_bad_creates(self):
|
|
args_list = [
|
|
(True,),
|
|
("/smith", 0),
|
|
("/smith", b"", "bleh"),
|
|
("/smith", b"", None, "fred"),
|
|
("/smith", b"", None, True, "fred"),
|
|
]
|
|
|
|
for args in args_list:
|
|
with pytest.raises(TypeError):
|
|
t = self.client.transaction()
|
|
t.create(*args)
|
|
|
|
def test_default_acl(self):
|
|
from kazoo.security import make_digest_acl
|
|
|
|
username = uuid.uuid4().hex
|
|
password = uuid.uuid4().hex
|
|
|
|
digest_auth = "%s:%s" % (username, password)
|
|
acl = make_digest_acl(username, password, all=True)
|
|
|
|
self.client.add_auth("digest", digest_auth)
|
|
self.client.default_acl = (acl,)
|
|
|
|
t = self.client.transaction()
|
|
t.create("/freddy")
|
|
results = t.commit()
|
|
assert results[0] == "/freddy"
|
|
|
|
def test_basic_delete(self):
|
|
self.client.create("/fred")
|
|
t = self.client.transaction()
|
|
t.delete("/fred")
|
|
results = t.commit()
|
|
assert results[0] is True
|
|
|
|
def test_bad_deletes(self):
|
|
args_list = [
|
|
(True,),
|
|
("/smith", "woops"),
|
|
]
|
|
|
|
for args in args_list:
|
|
with pytest.raises(TypeError):
|
|
t = self.client.transaction()
|
|
t.delete(*args)
|
|
|
|
def test_set(self):
|
|
self.client.create("/fred", b"01")
|
|
t = self.client.transaction()
|
|
t.set_data("/fred", b"oops")
|
|
t.commit()
|
|
res = self.client.get("/fred")
|
|
assert res[0] == b"oops"
|
|
|
|
def test_bad_sets(self):
|
|
args_list = [(42, 52), ("/smith", False), ("/smith", b"", "oops")]
|
|
|
|
for args in args_list:
|
|
with pytest.raises(TypeError):
|
|
t = self.client.transaction()
|
|
t.set_data(*args)
|
|
|
|
def test_check(self):
|
|
self.client.create("/fred")
|
|
version = self.client.get("/fred")[1].version
|
|
t = self.client.transaction()
|
|
t.check("/fred", version)
|
|
t.create("/blah")
|
|
results = t.commit()
|
|
assert results[0] is True
|
|
assert results[1] == "/blah"
|
|
|
|
def test_bad_checks(self):
|
|
args_list = [(42, 52), ("/smith", "oops")]
|
|
|
|
for args in args_list:
|
|
with pytest.raises(TypeError):
|
|
t = self.client.transaction()
|
|
t.check(*args)
|
|
|
|
def test_bad_transaction(self):
|
|
from kazoo.exceptions import RolledBackError, NoNodeError
|
|
|
|
t = self.client.transaction()
|
|
t.create("/fred")
|
|
t.delete("/smith")
|
|
results = t.commit()
|
|
assert results[0].__class__ == RolledBackError
|
|
assert results[1].__class__ == NoNodeError
|
|
|
|
def test_bad_commit(self):
|
|
t = self.client.transaction()
|
|
t.committed = True
|
|
|
|
with pytest.raises(ValueError):
|
|
t.commit()
|
|
|
|
def test_bad_context(self):
|
|
with pytest.raises(TypeError):
|
|
with self.client.transaction() as t:
|
|
t.check(4232)
|
|
|
|
def test_context(self):
|
|
with self.client.transaction() as t:
|
|
t.create("/smith", b"32")
|
|
assert self.client.get("/smith")[0] == b"32"
|
|
|
|
|
|
class TestSessionCallbacks(unittest.TestCase):
|
|
def test_session_callback_states(self):
|
|
from kazoo.protocol.states import KazooState, KeeperState
|
|
from kazoo.client import KazooClient
|
|
|
|
client = KazooClient()
|
|
client._handle = 1
|
|
client._live.set()
|
|
|
|
result = client._session_callback(KeeperState.CONNECTED)
|
|
assert result is None
|
|
|
|
# Now with stopped
|
|
client._stopped.set()
|
|
result = client._session_callback(KeeperState.CONNECTED)
|
|
assert result is None
|
|
|
|
# Test several state transitions
|
|
client._stopped.clear()
|
|
client.start_async = lambda: True
|
|
client._session_callback(KeeperState.CONNECTED)
|
|
assert client.state == KazooState.CONNECTED
|
|
|
|
client._session_callback(KeeperState.AUTH_FAILED)
|
|
assert client.state == KazooState.LOST
|
|
|
|
client._handle = 1
|
|
client._session_callback(-250)
|
|
assert client.state == KazooState.SUSPENDED
|
|
|
|
|
|
class TestCallbacks(KazooTestCase):
|
|
def test_async_result_callbacks_are_always_called(self):
|
|
# create a callback object
|
|
callback_mock = Mock()
|
|
|
|
# simulate waiting for a response
|
|
async_result = self.client.handler.async_result()
|
|
async_result.rawlink(callback_mock)
|
|
|
|
# begin the procedure to stop the client
|
|
self.client.stop()
|
|
|
|
# the response has just been received;
|
|
# this should be on another thread,
|
|
# simultaneously with the stop procedure
|
|
async_result.set_exception(
|
|
Exception("Anything that throws an exception")
|
|
)
|
|
|
|
# with the fix the callback should be called
|
|
assert callback_mock.call_count > 0
|
|
|
|
|
|
class TestNonChrootClient(KazooTestCase):
|
|
def test_create(self):
|
|
client = self._get_nonchroot_client()
|
|
assert client.chroot == ""
|
|
client.start()
|
|
node = uuid.uuid4().hex
|
|
path = client.create(node, ephemeral=True)
|
|
client.delete(path)
|
|
client.stop()
|
|
|
|
def test_unchroot(self):
|
|
client = self._get_nonchroot_client()
|
|
client.chroot = "/a"
|
|
# Unchroot'ing the chroot path should return "/"
|
|
assert client.unchroot("/a") == "/"
|
|
assert client.unchroot("/a/b") == "/b"
|
|
assert client.unchroot("/b/c") == "/b/c"
|
|
|
|
|
|
class TestReconfig(KazooTestCase):
|
|
def setUp(self):
|
|
KazooTestCase.setUp(self)
|
|
|
|
if CI_ZK_VERSION:
|
|
version = CI_ZK_VERSION
|
|
else:
|
|
version = self.client.server_version()
|
|
if not version or version < (3, 5):
|
|
pytest.skip("Must use Zookeeper 3.5 or above")
|
|
|
|
def test_no_super_auth(self):
|
|
with pytest.raises(NoAuthError):
|
|
self.client.reconfig(
|
|
joining="server.999=0.0.0.0:1234:2345:observer;3456",
|
|
leaving=None,
|
|
new_members=None,
|
|
)
|
|
|
|
def test_add_remove_observer(self):
|
|
def free_sock_port():
|
|
s = socket.socket()
|
|
s.bind(("", 0))
|
|
return s, s.getsockname()[1]
|
|
|
|
username = "super"
|
|
password = "test"
|
|
digest_auth = "%s:%s" % (username, password)
|
|
client = self._get_client(auth_data=[("digest", digest_auth)])
|
|
client.start()
|
|
|
|
# get ports for election, zab and client endpoints. we need to use
|
|
# ports for which we'd immediately get a RST upon connect(); otherwise
|
|
# the cluster could crash if it gets a SocketTimeoutException:
|
|
# https://issues.apache.org/jira/browse/ZOOKEEPER-2202
|
|
s1, port1 = free_sock_port()
|
|
s2, port2 = free_sock_port()
|
|
s3, port3 = free_sock_port()
|
|
|
|
joining = "server.100=0.0.0.0:%d:%d:observer;0.0.0.0:%d" % (
|
|
port1,
|
|
port2,
|
|
port3,
|
|
)
|
|
data, _ = client.reconfig(
|
|
joining=joining, leaving=None, new_members=None
|
|
)
|
|
assert joining.encode("utf8") in data
|
|
|
|
data, _ = client.reconfig(
|
|
joining=None, leaving="100", new_members=None
|
|
)
|
|
assert joining.encode("utf8") not in data
|
|
|
|
# try to add it again, but a config number in the future
|
|
curver = int(data.decode().split("\n")[-1].split("=")[1], base=16)
|
|
with pytest.raises(BadVersionError):
|
|
self.client.reconfig(
|
|
joining=joining,
|
|
leaving=None,
|
|
new_members=None,
|
|
from_config=curver + 1,
|
|
)
|
|
|
|
def test_bad_input(self):
|
|
with pytest.raises(BadArgumentsError):
|
|
self.client.reconfig(
|
|
joining="some thing", leaving=None, new_members=None
|
|
)
|