From 40460300c2a8b908aca08f9f713fc027393e24b3 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 13:50:09 +0200 Subject: [PATCH 01/26] Update comment to reflect correct redis version --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f32f0329..3dbae178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,9 @@ install: - pip install -e . - "if [[ $HIREDIS == '1' ]]; then pip install hiredis; fi" env: - # Redis 3.0.6 + # Redis 3.0.7 - HIREDIS=0 REDIS_VERSION=3.0 - # Redis 3.0.6 and HIREDIS + # Redis 3.0.7 and HIREDIS - HIREDIS=1 REDIS_VERSION=3.0 # Redis 3.2.0-rc3 - HIREDIS=0 REDIS_VERSION=3.2 From 4b5d73252920699d3b2e2bbd76d41ad2593e024d Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 14:17:57 +0200 Subject: [PATCH 02/26] Remove deprecated class RedisClusterMgt --- docs/release-notes.rst | 5 ++ docs/upgrading.rst | 2 +- rediscluster/cluster_mgt.py | 157 ------------------------------------ 3 files changed, 6 insertions(+), 158 deletions(-) delete mode 100644 rediscluster/cluster_mgt.py diff --git a/docs/release-notes.rst b/docs/release-notes.rst index b11555db..588a15a7 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -2,6 +2,11 @@ Release Notes ============= +1.3.0 (??? ??, ????) + + * Removed RedisClusterMgt class and file + + 1.2.0 (Apr 09, 2016) -------------------- diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 3c4769bb..06004399 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -7,7 +7,7 @@ This document describes what must be done when upgrading between different versi 1.2.0 --> Next release ---------------------- -Class RedisClusterMgt will be removed. +Class RedisClusterMgt has been removed. You should use the `CLUSTER ...` methods that exists in the `StrictRedisCluster` client class. diff --git a/rediscluster/cluster_mgt.py b/rediscluster/cluster_mgt.py deleted file mode 100644 index 7ff85282..00000000 --- a/rediscluster/cluster_mgt.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -import warnings -from collections import defaultdict - -from .connection import ClusterConnectionPool -from .exceptions import RedisClusterException -from .utils import clusterdown_wrapper, first_key, nslookup - -from redis.exceptions import ConnectionError, TimeoutError - - -class RedisClusterMgt(object): - """ - """ - blocked_args = ( - 'addslots', 'count_failure_reports', - 'countkeysinslot', 'delslots', 'failover', 'forget', - 'getkeysinslot', 'keyslot', 'meet', 'replicate', 'reset', - 'saveconfig', 'set_config_epoch', 'setslot', 'slaves', - ) - - def __init__(self, startup_nodes=None, **kwargs): - """ - """ - warnings.warn('RedisClusterMgt class will be removed in release 1.3.0. All CLUSTER commands is now implemented as commands in StrictRedisCluster class', FutureWarning) - - self.connection_pool = ClusterConnectionPool( - startup_nodes=startup_nodes, - init_slot_cache=True, **kwargs - ) - - def __getattr__(self, attr): - """ - """ - if attr in self.blocked_args: - raise RedisClusterException('{0} is currently not supported'.format(attr)) - - raise RedisClusterException('{0} is not a valid Redis cluster argument'.format(attr)) - - @clusterdown_wrapper - def _execute_command_on_nodes(self, nodes, *args, **kwargs): - """ - """ - command = args[0] - res = {} - - for node in nodes: - c = self.connection_pool.get_connection_by_node(node) - - try: - c.send_command(*args) - res[node["name"]] = c.read_response() - except (ConnectionError, TimeoutError) as e: - c.disconnect() - - if not c.retry_on_timeout and isinstance(e, TimeoutError): - raise - - c.send_command(*args) - res[node["name"]] = c.read_response() - finally: - self.connection_pool.release(c) - - return first_key(command, res) - - def _execute_cluster_commands(self, *args, **kwargs): - """ - """ - args = ('cluster',) + args - node = self.connection_pool.nodes.random_node() - - return self._execute_command_on_nodes([node], *args, **kwargs) - - def info(self): - """ - """ - raw = self._execute_cluster_commands('info') - - def _split(line): - k, v = line.split(':') - yield k - yield v - - return {k: v for k, v in - [_split(line) for line in raw.split('\r\n') if line]} - - def _make_host(self, host, port): - """ - """ - return '{0}:{1}'.format(host, port) - - def slots(self, host_required=False): - """ - """ - slots_info = self._execute_cluster_commands('slots') - master_slots = defaultdict(list) - slave_slots = defaultdict(list) - - for item in slots_info: - master_ip, master_port = item[2] - slots = [item[0], item[1]] - master_host = nslookup(master_ip) if host_required else master_ip - master_slots[self._make_host(master_host, master_port)].append(slots) - slaves = item[3:] - - for slave_ip, slave_port in slaves: - slave_host = nslookup(slave_ip) if host_required else slave_ip - slave_slots[self._make_host(slave_host, slave_port)].append(slots) - - return { - 'master': master_slots, - 'slave': slave_slots - } - - def _parse_node_line(self, line): - """ - """ - line_items = line.split(' ') - ret = line_items[:8] - slots = [sl.split('-') for sl in line_items[8:]] - ret.append(slots) - - return ret - - def nodes(self, host_required=False): - """ - """ - raw = self._execute_cluster_commands('nodes') - ret = {} - - for line in raw.split('\n'): - if not line: - continue - - node_id, ip_port, flags, master_id, ping, pong, epoch, \ - status, slots = self._parse_node_line(line) - role = flags - - if ',' in flags: - if "slave" in flags: - role = "slave" - elif "master" in flags: - role = "master" - - host = nslookup(ip_port) if host_required else ip_port - ret[host] = { - 'node_id': node_id, - 'role': role, - 'master_id': master_id, - 'last_ping_sent': ping, - 'last_pong_rcvd': pong, - 'epoch': epoch, - 'status': status, - 'slots': slots - } - - return ret From f9ea99fa2f1b138965a3f63171850b7d982abbb7 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 14:18:15 +0200 Subject: [PATCH 03/26] Bump version number to 1.3.0 --- rediscluster/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rediscluster/__init__.py b/rediscluster/__init__.py index 220fbd51..3e808e6b 100644 --- a/rediscluster/__init__.py +++ b/rediscluster/__init__.py @@ -17,7 +17,7 @@ setattr(redis, "StrictClusterPipeline", StrictClusterPipeline) # Major, Minor, Fix version -__version__ = (1, 2, 0) +__version__ = (1, 3, 0) if sys.version_info[0:3] == (3, 4, 0): raise RuntimeError("CRITICAL: rediscluster do not work with python 3.4.0. Please use 3.4.1 or higher.") diff --git a/setup.py b/setup.py index 4e8a2b3d..9bd8e769 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="redis-py-cluster", - version="1.2.0", + version="1.3.0", description="Cluster library for redis 3.0.0 built on top of redis-py lib", long_description=readme + '\n\n' + history, author="Johan Andersson", From 1803dcf2fd2762a1e4148795de6bf14bd5d22309 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 14:19:51 +0200 Subject: [PATCH 04/26] Remove RedisClusterMgt docs --- docs/cluster-mgt.rst | 52 -------------------------------------------- docs/index.rst | 1 - 2 files changed, 53 deletions(-) delete mode 100644 docs/cluster-mgt.rst diff --git a/docs/cluster-mgt.rst b/docs/cluster-mgt.rst deleted file mode 100644 index ec3b215c..00000000 --- a/docs/cluster-mgt.rst +++ /dev/null @@ -1,52 +0,0 @@ -Cluster Mgt class -================= - -The redis cluster can be managed through a cluster management class. It can for example be used to query the cluster for the current slots or the nodes setup. - -The following methods is implemented: - -- info -- slots -- nodes - -The following methods is not yet implemented: - -- addslots -- count_failure_reports -- countkeysinslot -- delslots -- failover -- forget -- getkeysinslot -- keyslot -- meet -- replicate -- reset -- saveconfig -- set_config_epoch -- setslot -- slaves - - - -Usage example -------------- - -.. code-block:: python - - >>> from rediscluster.cluster_mgt import RedisClusterMgt - >>> startup_nodes = [{"host": "127.0.0.1", "port": "7000"}] - >>> r = RedisClusterMgt(startup_nodes) - >>> r.slots() - { - 'slave': defaultdict(, { - '172.17.42.12:7003': [[0L, 5460L]], - '172.17.42.12:7005': [[10923L, 16383L]], - '172.17.42.12:7004': [[5461L, 10922L]] - }), - 'master': defaultdict(, { - '172.17.42.12:7002': [[10923L, 16383L]], - '172.17.42.12:7001': [[5461L, 10922L]], - '172.17.42.12:7000': [[0L, 5460L]] - }) - } diff --git a/docs/index.rst b/docs/index.rst index e34f3f53..5c41abde 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -92,7 +92,6 @@ The Usage Guide :maxdepth: 2 :glob: - cluster-mgt commands limitations-and-differences pipelines From ecc9616bf8f7c0a81ec28c7378b3917b44a5b321 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 14:20:03 +0200 Subject: [PATCH 05/26] Remove unusued import --- rediscluster/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rediscluster/__init__.py b/rediscluster/__init__.py index 3e808e6b..9f2299b0 100644 --- a/rediscluster/__init__.py +++ b/rediscluster/__init__.py @@ -5,7 +5,6 @@ # Import shortcut from .client import StrictRedisCluster, RedisCluster -from .cluster_mgt import RedisClusterMgt # NOQA from .pipeline import StrictClusterPipeline from .pubsub import ClusterPubSub From 5a449ccccea1f0d6c5d419ea1d78ab909e778a55 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 9 Apr 2016 14:22:48 +0200 Subject: [PATCH 06/26] Loosen the limitations of ptpdb and ptpython in dev-requirements --- dev-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index f4c7fc84..0d60c0e0 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,5 +7,5 @@ mock>=1.3.0,<2.0.0 docopt>=0.6.2,<1.0.0 tox>=2.2.0,<3.0.0 python-coveralls>=2.5.0,<3.0.0 -ptpdb==0.16 -ptpython==0.31 +ptpdb>=0.16,<1.0 +ptpython>=0.31,<1.0 From f2df6b8cd34bc5d042b70e5151be46a788b53574 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Thu, 21 Apr 2016 09:54:15 +0200 Subject: [PATCH 07/26] Remove testing of RedisClusterMgt --- tests/conftest.py | 10 +--------- tests/test_redis_cluster_mgt.py | 33 --------------------------------- 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 tests/test_redis_cluster_mgt.py diff --git a/tests/conftest.py b/tests/conftest.py index 55c6576c..87cbc01c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ import json # rediscluster imports -from rediscluster import StrictRedisCluster, RedisCluster, RedisClusterMgt +from rediscluster import StrictRedisCluster, RedisCluster # 3rd party imports import pytest @@ -148,11 +148,3 @@ def sr(request, *args, **kwargs): Returns a instance of StrictRedisCluster """ return _init_client(request, reinitialize_steps=1, cls=StrictRedisCluster, **kwargs) - - -@pytest.fixture() -def rcm(request, *args, **kwargs): - """ - Returns a instance of RedisClusterMgt - """ - return _init_mgt_client(request, cls=RedisClusterMgt, decode_responses=True, **kwargs) diff --git a/tests/test_redis_cluster_mgt.py b/tests/test_redis_cluster_mgt.py deleted file mode 100644 index cea7ba4a..00000000 --- a/tests/test_redis_cluster_mgt.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - - -class TestRedisClusterMgt(object): - - def test_info(self, rcm): - info = rcm.info() - assert 'cluster_state' in info - - def test_slots(self, rcm): - slots = rcm.slots() - assert 'master' in slots - assert 'slave' in slots - - master_slots = slots['master'] - for host, slots in master_slots.items(): - s = slots[0] - # node can have multiple slots - # as a result, the format is [[1, 2], [3, 4]] - assert isinstance(s, list) - assert len(s) == 2 - - def test_nodes(self, rcm): - nodes = rcm.nodes() - for host, info in nodes.items(): - assert 'node_id' in info - assert 'role' in info - assert 'master_id' in info - assert 'last_ping_sent' in info - assert 'last_pong_rcvd' in info - assert 'epoch' in info - assert 'status' in info - assert 'slots' in info From 7949ed3a6d753b5ab0584a76c738749eccff1925 Mon Sep 17 00:00:00 2001 From: Fabiano Tsuneo Maurer Ozahata Date: Wed, 20 Apr 2016 12:00:08 +0900 Subject: [PATCH 08/26] Fix a bug that was returned the key nodes_callbacks that was removed - The changes was made in the commit 4a953420e004385f6783b34ed43755e1e0c5ebd3. First was removed in line 146 but had other change in 1024 that returned the key. --- rediscluster/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rediscluster/client.py b/rediscluster/client.py index 9a41034a..19b61bc6 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -1020,7 +1020,6 @@ def pipeline(self, transaction=True, shard_hint=None): connection_pool=self.connection_pool, startup_nodes=self.connection_pool.nodes.startup_nodes, refresh_table_asap=self.refresh_table_asap, - nodes_callbacks=self.nodes_callbacks, response_callbacks=self.response_callbacks ) From 2a8c0bfc6eb51d511c029f9e88351fa19d601d6b Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 18 Jun 2016 09:48:59 +0200 Subject: [PATCH 09/26] Add regression test to verify that use of pipeline still works with that specific class --- docs/release-notes.rst | 1 + tests/test_pipeline.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 588a15a7..57438ca2 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -5,6 +5,7 @@ Release Notes 1.3.0 (??? ??, ????) * Removed RedisClusterMgt class and file + * Fixed a bug when using pipelines with RedisCluster class (Ozahata) 1.2.0 (Apr 09, 2016) diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index fbea1577..f0129630 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -8,6 +8,7 @@ from rediscluster.client import StrictRedisCluster from rediscluster.connection import ClusterConnectionPool, ClusterReadOnlyConnectionPool from rediscluster.exceptions import RedisClusterException +from tests.conftest import _get_client # 3rd party imports import pytest @@ -298,6 +299,14 @@ def test_blocked_arguments(self, r): assert unicode(ex.value).startswith("shard_hint is deprecated in cluster mode"), True + def test_redis_cluster_pipeline(self): + """ + Test that we can use a pipeline with the RedisCluster class + """ + r = _get_client(cls=None) + with r.pipeline(transaction=False) as pipe: + pipe.get("foobar") + def test_mget_disabled(self, r): with r.pipeline(transaction=False) as pipe: with pytest.raises(RedisClusterException): From 83d186c26110e681ab4887215393b748026ef4ec Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 18 Jun 2016 09:50:57 +0200 Subject: [PATCH 10/26] Add line about bumping version of redis-server during travis tests into the changelog --- docs/release-notes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 57438ca2..c4172dc3 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -6,6 +6,7 @@ Release Notes * Removed RedisClusterMgt class and file * Fixed a bug when using pipelines with RedisCluster class (Ozahata) + * Bump redis-server during travis tests to 3.0.7 1.2.0 (Apr 09, 2016) From 67241ecb811c6e32313d05624a60c7ab19ea3469 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sat, 18 Jun 2016 09:58:36 +0200 Subject: [PATCH 11/26] Add docs and release notes line about collision in module names with rediscluster project --- docs/index.rst | 22 ++++++++++++++++++++++ docs/release-notes.rst | 1 + 2 files changed, 23 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 5c41abde..bba309b4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -83,6 +83,28 @@ Experimental: +Regarding duplicate pypi and python naming +------------------------------------------ + +It has been found that the python module name that is used in this library (rediscluster) is already shared with a similar but older project. + +This lib will not change the naming of the module to something else to prevent collisions between the libs. + +My reasoning for this is the following + + - Changing the namespace is a major task and probably should only be done in a complete rewrite of the lib, or if the lib had plans for a version 2.0.0 where this kind of backwards incompatibility could be introduced. + - This project is more up to date, the last merged PR in the other project was 3 years ago. + - This project is aimed for implement support for the cluster support in 3.0+, the other lib do not have that right now, but they implement almost the same cluster solution as the 3.0+ but in much more in the client side. + - The 2 libs is not compatible to be run at the same time even if the name would not collide. It is not recommended to run both in the same python interpreter. + +An issue has been raised in each repository to have tracking of the problem. + +redis-py-cluster: https://github.com/Grokzen/redis-py-cluster/issues/150 + +rediscluster: https://github.com/salimane/rediscluster-py/issues/11 + + + The Usage Guide --------------- diff --git a/docs/release-notes.rst b/docs/release-notes.rst index c4172dc3..22660b66 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -7,6 +7,7 @@ Release Notes * Removed RedisClusterMgt class and file * Fixed a bug when using pipelines with RedisCluster class (Ozahata) * Bump redis-server during travis tests to 3.0.7 + * Added docs about same module name in another python redis cluster project. 1.2.0 (Apr 09, 2016) From 09145d59fea8cd9ef96a402dc6c9f0d6aa8a5f06 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 11:55:48 +0200 Subject: [PATCH 12/26] Fix KeyError on node['name'] not in self._in_use_connections (ashishbaghudana) --- docs/release-notes.rst | 2 ++ rediscluster/connection.py | 4 ++++ tests/test_cluster_connection_pool.py | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 22660b66..8440f202 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -8,6 +8,8 @@ Release Notes * Fixed a bug when using pipelines with RedisCluster class (Ozahata) * Bump redis-server during travis tests to 3.0.7 * Added docs about same module name in another python redis cluster project. + * Fix a bug when a connection was to be tracked for a node but the node either do not yet exists or + was removed because of resharding was done in another thread. (ashishbaghudana) 1.2.0 (Apr 09, 2016) diff --git a/rediscluster/connection.py b/rediscluster/connection.py index 5a7dbe3f..a645de4c 100644 --- a/rediscluster/connection.py +++ b/rediscluster/connection.py @@ -146,6 +146,10 @@ def get_connection(self, command_name, *keys, **options): connection = self._available_connections.get(node["name"], []).pop() except IndexError: connection = self.make_connection(node) + + if node['name'] not in self._in_use_connections: + self._in_use_connections[node['name']] = set() + self._in_use_connections[node['name']].add(connection) return connection diff --git a/tests/test_cluster_connection_pool.py b/tests/test_cluster_connection_pool.py index 887a5762..bb69bb01 100644 --- a/tests/test_cluster_connection_pool.py +++ b/tests/test_cluster_connection_pool.py @@ -46,6 +46,15 @@ def get_pool(self, connection_kwargs=None, max_connections=None, max_connections **connection_kwargs) return pool + def test_in_use_not_exists(self): + """ + Test that if for some reason, the node that it tries to get the connectino for + do not exists in the _in_use_connection variable. + """ + pool = self.get_pool() + pool._in_use_connections = {} + pool.get_connection("pubsub", channel="foobar") + def test_connection_creation(self): connection_kwargs = {'foo': 'bar', 'biz': 'baz'} pool = self.get_pool(connection_kwargs=connection_kwargs) From 97e3f66e376eb2d98edd1a97223e248f26fea526 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 12:30:56 +0200 Subject: [PATCH 13/26] Fixed a bug with "CLUSTER ..." commands when a node_id argument was needed and the return type was supposed to be converted to bool with bool_ok in redis._compat. --- docs/release-notes.rst | 2 ++ rediscluster/client.py | 3 ++- rediscluster/utils.py | 10 +++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 8440f202..14dbed5a 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -10,6 +10,8 @@ Release Notes * Added docs about same module name in another python redis cluster project. * Fix a bug when a connection was to be tracked for a node but the node either do not yet exists or was removed because of resharding was done in another thread. (ashishbaghudana) + * Fixed a bug with "CLUSTER ..." commands when a node_id argument was needed and the return type + was supposed to be converted to bool with bool_ok in redis._compat. 1.2.0 (Apr 09, 2016) diff --git a/rediscluster/client.py b/rediscluster/client.py index 19b61bc6..d5a0275e 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -14,6 +14,7 @@ ) from .pubsub import ClusterPubSub from .utils import ( + bool_ok, string_keys_to_dict, dict_merge, blocked_command, @@ -25,7 +26,7 @@ ) # 3rd party imports from redis import StrictRedis -from redis.client import list_or_args, bool_ok, parse_info +from redis.client import list_or_args, parse_info from redis.connection import Token from redis._compat import iteritems, basestring, b, izip, nativestr from redis.exceptions import RedisError, ResponseError, TimeoutError, DataError, ConnectionError, BusyLoadingError diff --git a/rediscluster/utils.py b/rediscluster/utils.py index 7c2c7b06..ef2cbb14 100644 --- a/rediscluster/utils.py +++ b/rediscluster/utils.py @@ -8,7 +8,15 @@ ) # 3rd party imports -from redis._compat import basestring +from redis._compat import basestring, nativestr + + +def bool_ok(response, *args, **kwargs): + """ + Borrowed from redis._compat becuase that method to not support extra arguments + when used in a cluster environment. + """ + return nativestr(response) == 'OK' def string_keys_to_dict(key_strings, callback): From 5a9bcf3f4cc5b2e23fe0522e4062af56e5fa3899 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 13:42:50 +0200 Subject: [PATCH 14/26] Add gitter chat room link that was missing from readme file --- README.md | 2 ++ docs/release-notes.rst | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index abeebc60..4d5c43ae 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This client provides a client for redis cluster that was added in redis 3.0. This project is a port of `redis-rb-cluster` by antirez, with alot of added functionality. The original source can be found at https://github.com/antirez/redis-rb-cluster +Gitter chat room: [![Gitter](https://badges.gitter.im/Grokzen/redis-py-cluster.svg)](https://gitter.im/Grokzen/redis-py-cluster?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + [![Build Status](https://travis-ci.org/Grokzen/redis-py-cluster.svg?branch=master)](https://travis-ci.org/Grokzen/redis-py-cluster) [![Coverage Status](https://coveralls.io/repos/Grokzen/redis-py-cluster/badge.png)](https://coveralls.io/r/Grokzen/redis-py-cluster) [![PyPI version](https://badge.fury.io/py/redis-py-cluster.svg)](http://badge.fury.io/py/redis-py-cluster) [![Code Health](https://landscape.io/github/Grokzen/redis-py-cluster/unstable/landscape.svg)](https://landscape.io/github/Grokzen/redis-py-cluster/unstable) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 14dbed5a..3c7e420c 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -12,6 +12,7 @@ Release Notes was removed because of resharding was done in another thread. (ashishbaghudana) * Fixed a bug with "CLUSTER ..." commands when a node_id argument was needed and the return type was supposed to be converted to bool with bool_ok in redis._compat. + * Add back gitter chat room link 1.2.0 (Apr 09, 2016) From 1230491255edcdee6cd983f8d0635979afbc867c Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 13:46:24 +0200 Subject: [PATCH 15/26] Remove landscape.io badge link because it is no longer used --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d5c43ae..c8538702 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This project is a port of `redis-rb-cluster` by antirez, with alot of added func Gitter chat room: [![Gitter](https://badges.gitter.im/Grokzen/redis-py-cluster.svg)](https://gitter.im/Grokzen/redis-py-cluster?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Build Status](https://travis-ci.org/Grokzen/redis-py-cluster.svg?branch=master)](https://travis-ci.org/Grokzen/redis-py-cluster) [![Coverage Status](https://coveralls.io/repos/Grokzen/redis-py-cluster/badge.png)](https://coveralls.io/r/Grokzen/redis-py-cluster) [![PyPI version](https://badge.fury.io/py/redis-py-cluster.svg)](http://badge.fury.io/py/redis-py-cluster) [![Code Health](https://landscape.io/github/Grokzen/redis-py-cluster/unstable/landscape.svg)](https://landscape.io/github/Grokzen/redis-py-cluster/unstable) +[![Build Status](https://travis-ci.org/Grokzen/redis-py-cluster.svg?branch=master)](https://travis-ci.org/Grokzen/redis-py-cluster) [![Coverage Status](https://coveralls.io/repos/Grokzen/redis-py-cluster/badge.png)](https://coveralls.io/r/Grokzen/redis-py-cluster) [![PyPI version](https://badge.fury.io/py/redis-py-cluster.svg)](http://badge.fury.io/py/redis-py-cluster) From e857aea26b2c090728551e8112f1e8cd3a15bbfb Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 13:50:26 +0200 Subject: [PATCH 16/26] Add new command cluster_reset_all_nodes() --- docs/release-notes.rst | 2 ++ rediscluster/client.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 3c7e420c..4a5eb273 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -13,6 +13,8 @@ Release Notes * Fixed a bug with "CLUSTER ..." commands when a node_id argument was needed and the return type was supposed to be converted to bool with bool_ok in redis._compat. * Add back gitter chat room link + * Add new client commands + - cluster_reset_all_nodes 1.2.0 (Apr 09, 2016) diff --git a/rediscluster/client.py b/rediscluster/client.py index d5a0275e..645e115d 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -431,6 +431,22 @@ def cluster_reset(self, node_id, soft=True): """ return self.execute_command('CLUSTER RESET', Token('SOFT' if soft else 'HARD'), node_id=node_id) + def cluster_reset_all_nodes(self, soft=True): + """ + Send CLUSTER RESET to all nodes in the cluster. + + If 'soft' is True then it will send 'SOFT' argument + If 'soft' is False then it will send 'HARD' argument + """ + return [ + self.execute_command( + 'CLUSTER RESET', + Token('SOFT' if soft else 'HARD'), + node_id=node['id'], + ) + for node in self.cluster_nodes() + ] + # Send to all nodes def cluster_save_config(self): """Forces the node to save cluster state on disk""" From 4d07bbe61cf55ab88e29864cd4f45e07252b465a Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 3 Jul 2016 16:44:00 +0200 Subject: [PATCH 17/26] Cleanup docstring for all cluster managmenet commands --- rediscluster/client.py | 115 +++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/rediscluster/client.py b/rediscluster/client.py index 645e115d..0f837cc8 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -371,72 +371,106 @@ def _execute_command_on_nodes(self, nodes, *args, **kwargs): ########## # Cluster management commands - # Send to specefied node def cluster_addslots(self, node_id, *slots): - """Assign new hash slots to receiving node""" + """ + Assign new hash slots to receiving node + + Sends to specefied node + """ return self.execute_command('CLUSTER ADDSLOTS', *slots, node_id=node_id) - # Send to node based on slot_id def cluster_countkeysinslot(self, slot_id): - """Return the number of local keys in the specified hash slot""" + """ + Return the number of local keys in the specified hash slot + + Send to node based on specefied slot_id + """ return self.execute_command('CLUSTER COUNTKEYSINSLOT', slot_id) - # Send to specefied node def cluster_count_failure_report(self, node_id): - """Return the number of failure reports active for a given node""" + """ + Return the number of failure reports active for a given node + + Sends to specefied node + """ return self.execute_command('CLUSTER COUNT-FAILURE-REPORTS', node_id=node_id) - # Send to specefied node def cluster_delslots(self, node_id, *slots): - """Set hash slots as unbound in receiving node""" + """ + Set hash slots as unbound in receiving node + + Sends to specefied node + """ return self.execute_command('CLUSTER DELSLOTS', *slots, node_id=node_id) - # Send to specefied node def cluster_failover(self, node_id, option): - """Forces a slave to perform a manual failover of its master.""" + """ + Forces a slave to perform a manual failover of its master + + Sends to specefied node + """ assert option.upper() in ('FORCE', 'TAKEOVER') # TODO: change this option handling return self.execute_command('CLUSTER FAILOVER', Token(option)) - # Send to random node def cluster_info(self): - """Provides info about Redis Cluster node state""" + """ + Provides info about Redis Cluster node state + + Sends to random node in the cluster + """ return self.execute_command('CLUSTER INFO') - # Send to random node def cluster_keyslot(self, name): - """Returns the hash slot of the specified key""" + """ + Returns the hash slot of the specified key + + Sends to random node in the cluster + """ return self.execute_command('CLUSTER KEYSLOT', name) - # Send to specefied node def cluster_meet(self, node_id, host, port): - """Force a node cluster to handshake with another node""" + """ + Force a node cluster to handshake with another node. + + Sends to specefied node + """ return self.execute_command('CLUSTER MEET', host, port, node_id=node_id) - # Send to random node def cluster_nodes(self): - """Force a node cluster to handshake with another node""" + """ + Force a node cluster to handshake with another node + + Sends to random node in the cluster + """ return self.execute_command('CLUSTER NODES') - # Send to specefied node def cluster_replicate(self, target_node_id): - """Reconfigure a node as a slave of the specified master node""" + """ + Reconfigure a node as a slave of the specified master node + + Sends to specefied node + """ return self.execute_command('CLUSTER REPLICATE', target_node_id) - # Send to specific node def cluster_reset(self, node_id, soft=True): """ - Reset a Redis Cluster node. + Reset a Redis Cluster node + If 'soft' is True then it will send 'SOFT' argument If 'soft' is False then it will send 'HARD' argument + + Sends to specefied node """ return self.execute_command('CLUSTER RESET', Token('SOFT' if soft else 'HARD'), node_id=node_id) def cluster_reset_all_nodes(self, soft=True): """ - Send CLUSTER RESET to all nodes in the cluster. + Send CLUSTER RESET to all nodes in the cluster If 'soft' is True then it will send 'SOFT' argument If 'soft' is False then it will send 'HARD' argument + + Sends to all nodes in the cluster """ return [ self.execute_command( @@ -447,20 +481,29 @@ def cluster_reset_all_nodes(self, soft=True): for node in self.cluster_nodes() ] - # Send to all nodes def cluster_save_config(self): - """Forces the node to save cluster state on disk""" + """ + Forces the node to save cluster state on disk + + Sends to all nodes in the cluster + """ return self.execute_command('CLUSTER SAVECONFIG') - # Send to specefied node_id def cluster_set_config_epoch(self, node_id, epoch): - """Set the configuration epoch in a new node""" + """ + Set the configuration epoch in a new node + + Sends to specefied node + """ return self.execute_command('CLUSTER SET-CONFIG-EPOCH', epoch, node_id=node_id) - # Send to specefied node_id # TODO: Determine what the purpose of bind_to_node_ip is going to be def cluster_setslot(self, node_id, slot_id, state, bind_to_node_id=None): - """Bind an hash slot to a specific node""" + """ + Bind an hash slot to a specific node + + Sends to specefied node + """ if state.upper() in ('IMPORTING', 'MIGRATING', 'NODE') and node_id is not None: return self.execute_command('CLUSTER SETSLOT', slot_id, Token(state), node_id) elif state.upper() == 'STABLE': @@ -468,14 +511,20 @@ def cluster_setslot(self, node_id, slot_id, state, bind_to_node_id=None): else: raise RedisError('Invalid slot state: {0}'.format(state)) - # Specefied node def cluster_slaves(self, target_node_id): - """Force a node cluster to handshake with another node""" + """ + Force a node cluster to handshake with another node + + Sends to targeted cluster node + """ return self.execute_command('CLUSTER SLAVES', target_node_id) - # Random node def cluster_slots(self): - """Get array of Cluster slot to node mappings""" + """ + Get array of Cluster slot to node mappings + + Sends to random node in the cluster + """ return self.execute_command('CLUSTER SLOTS') ########## From c9648106880c32a511a51c670a00fd3dc6466495 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Thu, 7 Jul 2016 18:24:02 +0200 Subject: [PATCH 18/26] Rebuild command cluster_delslots to determine what cluster shard each slot is located and send the delete command for each slot to correct node. --- docs/release-notes.rst | 2 ++ docs/upgrading.rst | 2 ++ rediscluster/client.py | 29 +++++++++++++++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 4a5eb273..ae180aa6 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -15,6 +15,8 @@ Release Notes * Add back gitter chat room link * Add new client commands - cluster_reset_all_nodes + * Command cluster_delslots now determines what cluster shard each slot is on and sends each slot deletion + command to the correct node. Command have changed argument spec (Read Upgrading.rst for details) 1.2.0 (Apr 09, 2016) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 06004399..2967f93d 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -9,6 +9,8 @@ This document describes what must be done when upgrading between different versi Class RedisClusterMgt has been removed. You should use the `CLUSTER ...` methods that exists in the `StrictRedisCluster` client class. +Method `cluster_delslots` changed argument specification from `self, node_id, *slots` to `self, node_id=None, *slots` and changed the behaviour of the method to now automatically determine the slot_id based on the current cluster structure and where each slot that you want to delete is loated. + 1.1.0 --> 1.2.0 diff --git a/rediscluster/client.py b/rediscluster/client.py index 0f837cc8..528248b6 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -371,6 +371,21 @@ def _execute_command_on_nodes(self, nodes, *args, **kwargs): ########## # Cluster management commands + def _nodes_slots_to_slots_nodes(self, mapping): + """ + Converts a mapping of + {id: , slots: (slot1, slot2)} + to + {slot1: , slot2: } + + Operation is expensive so use with caution + """ + out = {} + for node in mapping: + for slot in node['slots']: + out[str(slot)] = node['id'] + return out + def cluster_addslots(self, node_id, *slots): """ Assign new hash slots to receiving node @@ -395,13 +410,19 @@ def cluster_count_failure_report(self, node_id): """ return self.execute_command('CLUSTER COUNT-FAILURE-REPORTS', node_id=node_id) - def cluster_delslots(self, node_id, *slots): + def cluster_delslots(self, node_id=None, *slots): """ - Set hash slots as unbound in receiving node + Set hash slots as unbound in the cluster. + It determines by it self what node the slot is in and sends it there - Sends to specefied node + Returns a list of the results for each processed slot. """ - return self.execute_command('CLUSTER DELSLOTS', *slots, node_id=node_id) + cluster_nodes = self._nodes_slots_to_slots_nodes(self.cluster_nodes()) + + return [ + self.execute_command('CLUSTER DELSLOTS', slot, node_id=cluster_nodes[slot]) + for slot in slots + ] def cluster_failover(self, node_id, option): """ From e12803f036a7bfb854f64374c61b49eb122f8e8f Mon Sep 17 00:00:00 2001 From: Grokzen Date: Thu, 7 Jul 2016 18:40:09 +0200 Subject: [PATCH 19/26] Remove node_id=None from cluster_delslots --- docs/upgrading.rst | 2 +- rediscluster/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 2967f93d..14e26bf4 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -9,7 +9,7 @@ This document describes what must be done when upgrading between different versi Class RedisClusterMgt has been removed. You should use the `CLUSTER ...` methods that exists in the `StrictRedisCluster` client class. -Method `cluster_delslots` changed argument specification from `self, node_id, *slots` to `self, node_id=None, *slots` and changed the behaviour of the method to now automatically determine the slot_id based on the current cluster structure and where each slot that you want to delete is loated. +Method `cluster_delslots` changed argument specification from `self, node_id, *slots` to `self, *slots` and changed the behaviour of the method to now automatically determine the slot_id based on the current cluster structure and where each slot that you want to delete is loated. diff --git a/rediscluster/client.py b/rediscluster/client.py index 528248b6..3f7fa013 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -410,7 +410,7 @@ def cluster_count_failure_report(self, node_id): """ return self.execute_command('CLUSTER COUNT-FAILURE-REPORTS', node_id=node_id) - def cluster_delslots(self, node_id=None, *slots): + def cluster_delslots(self, *slots): """ Set hash slots as unbound in the cluster. It determines by it self what node the slot is in and sends it there From ea97c633e74185b2a5e812264ca7d5cc241552e4 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Thu, 7 Jul 2016 19:05:41 +0200 Subject: [PATCH 20/26] Fix a bug with cluster_slots command where it would cause exception when used --- rediscluster/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rediscluster/utils.py b/rediscluster/utils.py index ef2cbb14..b275d6db 100644 --- a/rediscluster/utils.py +++ b/rediscluster/utils.py @@ -133,8 +133,8 @@ def fix_server(host, port): start, end, master = slot[:3] slaves = slot[3:] slots[start, end] = { - 'master': fix_server(master), - 'slaves': [fix_server(slave) for slave in slaves], + 'master': fix_server(*master), + 'slaves': [fix_server(*slave) for slave in slaves], } return slots From 802436a9904869fccc7576e8129ee98be0dafb6b Mon Sep 17 00:00:00 2001 From: Grokzen Date: Thu, 7 Jul 2016 19:20:46 +0200 Subject: [PATCH 21/26] Remove the custom logic for pfcount to prevent and raise custom exception when pfcount is used with crossslots. --- docs/upgrading.rst | 2 ++ rediscluster/client.py | 13 +------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 14e26bf4..aa3a7a35 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -11,6 +11,8 @@ Class RedisClusterMgt has been removed. You should use the `CLUSTER ...` methods Method `cluster_delslots` changed argument specification from `self, node_id, *slots` to `self, *slots` and changed the behaviour of the method to now automatically determine the slot_id based on the current cluster structure and where each slot that you want to delete is loated. +Method pfcount no longer has custom logic and exceptions to prevent CROSSSLOT errors. If method is used with different slots then a regular CROSSSLOT error (rediscluster.exceptions.ClusterCrossSlotError) will be returned. + 1.1.0 --> 1.2.0 diff --git a/rediscluster/client.py b/rediscluster/client.py index 3f7fa013..ee524243 100644 --- a/rediscluster/client.py +++ b/rediscluster/client.py @@ -987,22 +987,11 @@ def sunionstore(self, dest, keys, *args): return self.sadd(dest, *res) - def ensure_same_slot(self, keys): - """ - Returns True if all slots for the list of keys point - to the same hash slot. - """ - slots = [self.connection_pool.nodes.keyslot(key) for key in keys] - return len(slots) == 1 - def pfcount(self, *sources): """ pfcount only works when all sources point to the same hash slot. """ - if self.ensure_same_slot(sources): - return super(self.__class__, self).pfcount(*sources) - else: - raise RedisClusterException("pfcount can't be used when sources point to different hashslots") + return super(self.__class__, self).pfcount(*sources) def pfmerge(self, dest, *sources): """ From 3b4de9c84bcbab70a1557344c61c7cfde76fc06f Mon Sep 17 00:00:00 2001 From: Yongzhi Pan Date: Sat, 6 Aug 2016 19:10:47 +0800 Subject: [PATCH 22/26] Fix for Python 3 bytes key. fix #153. --- docs/release-notes.rst | 1 + rediscluster/nodemanager.py | 35 ++++++++++++++++++++++++++++++++--- tests/test_node_manager.py | 3 +++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index ae180aa6..12d90767 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -17,6 +17,7 @@ Release Notes - cluster_reset_all_nodes * Command cluster_delslots now determines what cluster shard each slot is on and sends each slot deletion command to the correct node. Command have changed argument spec (Read Upgrading.rst for details) + * Fixed a bug when hashing the key it if was a python 3 byte string and it would cause it to route to wrong slot in the cluster (fossilet, Grokzen) 1.2.0 (Apr 09, 2016) diff --git a/rediscluster/nodemanager.py b/rediscluster/nodemanager.py index 61b12ced..5abf9c7c 100644 --- a/rediscluster/nodemanager.py +++ b/rediscluster/nodemanager.py @@ -2,6 +2,7 @@ # python std lib import random +import sys # rediscluster imports from .crc import crc16 @@ -30,13 +31,41 @@ def __init__(self, startup_nodes=None, **connection_kwargs): if not self.startup_nodes: raise RedisClusterException("No startup nodes provided") - def keyslot(self, key): + # Minor performance tweak to avoid having to check inside the method + # for each call to keyslot method. + if sys.version_info[0] < 3: + self.keyslot = self.keyslot_py_2 + else: + self.keyslot = self.keyslot_py_3 + + def keyslot_py_2(self, key): """ Calculate keyslot for a given key. - - This also works for binary keys that is used in python 3. + Tuned for compatibility with python 2.7.x """ k = unicode(key) + + start = k.find("{") + + if start > -1: + end = k.find("}", start + 1) + if end > -1 and end != start + 1: + k = k[start + 1:end] + + return crc16(k) % self.RedisClusterHashSlots + + def keyslot_py_3(self, key): + """ + Calculate keyslot for a given key. + Tuned for compatibility with supported python 3.x versions + """ + try: + # Handle bytes case + k = str(key, encoding='utf-8') + except TypeError: + # Convert others to str. + k = str(key) + start = k.find("{") if start > -1: diff --git a/tests/test_node_manager.py b/tests/test_node_manager.py index 4a1291e4..6a9677a6 100644 --- a/tests/test_node_manager.py +++ b/tests/test_node_manager.py @@ -39,6 +39,9 @@ def test_keyslot(): assert n.keyslot("{foo}bar") == 12182 assert n.keyslot("{foo}") == 12182 assert n.keyslot(1337) == 4314 + assert n.keyslot(b"abc") == n.keyslot("abc") + assert n.keyslot("abc") == n.keyslot(unicode("abc")) + assert n.keyslot(unicode("abc")) == n.keyslot(b"abc") def test_init_slots_cache_not_all_slots(s): From b19ec60a770776cb022b009505497b44e35183be Mon Sep 17 00:00:00 2001 From: monklof Date: Thu, 8 Sep 2016 12:10:34 +0800 Subject: [PATCH 23/26] choose the currently dicovered node to get cluster-require-full-coverage config instead of the old ones --- rediscluster/nodemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rediscluster/nodemanager.py b/rediscluster/nodemanager.py index 5abf9c7c..762ead03 100644 --- a/rediscluster/nodemanager.py +++ b/rediscluster/nodemanager.py @@ -244,7 +244,7 @@ def cluster_require_full_coverage(self, nodes_cache): then even all slots are not covered, cluster still will be able to respond """ - nodes = self.nodes or nodes_cache + nodes = nodes_cache or self.nodes def node_require_full_coverage(node): r_node = self.get_redis_link(host=node["host"], port=node["port"], decode_responses=True) From f5e53a6c23b28079d8e911c62829c3d7990c199f Mon Sep 17 00:00:00 2001 From: monklof Date: Thu, 8 Sep 2016 12:28:48 +0800 Subject: [PATCH 24/26] add author monklof --- docs/authors.rst | 1 + docs/release-notes.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/authors.rst b/docs/authors.rst index e29438d3..2d64a2c7 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -19,3 +19,4 @@ Authors who contributed code or testing: - etng - https://github.com/etng - gmolight - https://github.com/gmolight - baranbartu - https://github.com/baranbartu + - monklof - https://github.com/monklof diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 12d90767..6cd8de5d 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -18,6 +18,7 @@ Release Notes * Command cluster_delslots now determines what cluster shard each slot is on and sends each slot deletion command to the correct node. Command have changed argument spec (Read Upgrading.rst for details) * Fixed a bug when hashing the key it if was a python 3 byte string and it would cause it to route to wrong slot in the cluster (fossilet, Grokzen) + * Fixed a bug when reinitialize the nodemanager it would use the old nodes_cache instead of the new one that was just parsed (monklof) 1.2.0 (Apr 09, 2016) From 60111c74563e583a5c9602f17eb7b33838f5d6d7 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 11 Sep 2016 15:25:23 +0200 Subject: [PATCH 25/26] Add a line in readme linking where the changelog is to make it easier to find --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c8538702..fb7534fa 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ All documentation can be found at http://redis-py-cluster.readthedocs.org/en/mas This Readme contains a reduced version of the full documentation. -Upgrading instructions between each released version can be found [docs/upgrading.rst](Here) +Upgrading instructions between each released version can be found [here](docs/upgrading.rst) + +Changelog for next release and all older releasess can be found [here](docs/release-notes.rst) From 263ed1ed5d5ce86d96b2ef79f45bdf173ee184e3 Mon Sep 17 00:00:00 2001 From: Grokzen Date: Sun, 11 Sep 2016 19:20:05 +0200 Subject: [PATCH 26/26] Release prep --- docs/release-notes.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 6cd8de5d..66fb9cc2 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -2,7 +2,8 @@ Release Notes ============= -1.3.0 (??? ??, ????) +1.3.0 (Sep 11, 2016) +-------------------- * Removed RedisClusterMgt class and file * Fixed a bug when using pipelines with RedisCluster class (Ozahata)