# Path: aredis/connection.py
# class ClusterConnection(Connection):
#     "Manages TCP communication to and from a Redis server"
#     description = "ClusterConnection<host={host},port={port}>"
# 
#     def __init__(self, *args, **kwargs):
#         self.readonly = kwargs.pop('readonly', False)
#         super(ClusterConnection, self).__init__(*args, **kwargs)
# 
#     async def on_connect(self):
#         """
#         Initialize the connection, authenticate and select a database and send READONLY if it is
#         set during object initialization.
#         """
#         if self.db:
#             warnings.warn('SELECT DB is not allowed in cluster mode')
#             self.db = ''
#         await super(ClusterConnection, self).on_connect()
#         if self.readonly:
#             await self.send_command('READONLY')
#             if nativestr(await self.read_response()) != 'OK':
#                 raise ConnectionError('READONLY command failed')
# 
# Path: aredis/exceptions.py
# class LockError(RedisError, ValueError):
#     """Errors acquiring or releasing a lock"""
#     # NOTE: For backwards compatability, this class derives from ValueError.
#     # This was originally chosen to behave like threading.Lock.
#     pass
# 
# class WatchError(RedisError):
#     pass
# 
# Path: aredis/utils.py
# def b(x):
#     return x.encode('latin-1') if not isinstance(x, bytes) else x
# 
# class dummy:
#     """
#     Instances of this class can be used as an attribute container.
#     """
# 
#     def __init__(self):
#         self.token = None
# 
#     def set(self, value):
#         self.token = value
# 
#     def get(self):
#         return self.token


import asyncio
import time as mod_time
import uuid
import warnings
import contextvars
from aredis.connection import ClusterConnection
from aredis.exceptions import LockError, WatchError
from aredis.utils import b, dummy
    For more discussion please see:
    https://github.com/NoneGG/aredis/issues/55

    My solution is to take advantage of `READONLY` mode of slaves to ensure
    the lock key is synced from master to N/2 + 1 of its slaves to avoid the fail-over problem.
    Since it is a single-key solution, the migration problem also do no matter.

    Please read these article below before using this cluster lock in your app.
    https://redis.io/topics/distlock
    http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
    http://antirez.com/news/101
    """

    def __init__(self, *args, **kwargs):
        super(ClusterLock, self).__init__(*args, **kwargs)
        if not self.timeout:
            raise LockError('timeout must be provided for cluster lock')

    async def check_lock_in_slaves(self, token):
        node_manager = self.redis.connection_pool.nodes
        slot = node_manager.keyslot(self.name)
        master_node_id = node_manager.node_from_slot(slot)['node_id']
        slave_nodes = await self.redis.cluster_slaves(master_node_id)
        count, quorum = 0, (len(slave_nodes) // 2) + 1
        conn_kwargs = self.redis.connection_pool.connection_kwargs
        conn_kwargs['readonly'] = True
        for node in slave_nodes:
            try:
                # todo: a little bit dirty here, try to reuse StrictRedis later
                # todo: it may be optimized by using a new connection pool

# Path: aredis/connection.py
# class ClusterConnection(Connection):
#     "Manages TCP communication to and from a Redis server"
#     description = "ClusterConnection<host={host},port={port}>"
# 
#     def __init__(self, *args, **kwargs):
#         self.readonly = kwargs.pop('readonly', False)
#         super(ClusterConnection, self).__init__(*args, **kwargs)
# 
#     async def on_connect(self):
#         """
#         Initialize the connection, authenticate and select a database and send READONLY if it is
#         set during object initialization.
#         """
#         if self.db:
#             warnings.warn('SELECT DB is not allowed in cluster mode')
#             self.db = ''
#         await super(ClusterConnection, self).on_connect()
#         if self.readonly:
#             await self.send_command('READONLY')
#             if nativestr(await self.read_response()) != 'OK':
#                 raise ConnectionError('READONLY command failed')
# 
# Path: aredis/exceptions.py
# class LockError(RedisError, ValueError):
#     """Errors acquiring or releasing a lock"""
#     # NOTE: For backwards compatability, this class derives from ValueError.
#     # This was originally chosen to behave like threading.Lock.
#     pass
# 
# class WatchError(RedisError):
#     pass
# 
# Path: aredis/utils.py
# def b(x):
#     return x.encode('latin-1') if not isinstance(x, bytes) else x
# 
# class dummy:
#     """
#     Instances of this class can be used as an attribute container.
#     """
# 
#     def __init__(self):
#         self.token = None
# 
#     def set(self, value):
#         self.token = value
# 
#     def get(self):
#         return self.token


# Path: aredis/lock.py
import asyncio
import time as mod_time
import uuid
import warnings
import contextvars
from aredis.connection import ClusterConnection
from aredis.exceptions import LockError, WatchError
from aredis.utils import b, dummy

    For more discussion please see:
    https://github.com/NoneGG/aredis/issues/55

    My solution is to take advantage of `READONLY` mode of slaves to ensure
    the lock key is synced from master to N/2 + 1 of its slaves to avoid the fail-over problem.
    Since it is a single-key solution, the migration problem also do no matter.

    Please read these article below before using this cluster lock in your app.
    https://redis.io/topics/distlock
    http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
    http://antirez.com/news/101
    """

    def __init__(self, *args, **kwargs):
        super(ClusterLock, self).__init__(*args, **kwargs)
        if not self.timeout:
            raise LockError('timeout must be provided for cluster lock')

    async def check_lock_in_slaves(self, token):
        node_manager = self.redis.connection_pool.nodes
        slot = node_manager.keyslot(self.name)
        master_node_id = node_manager.node_from_slot(slot)['node_id']
        slave_nodes = await self.redis.cluster_slaves(master_node_id)
        count, quorum = 0, (len(slave_nodes) // 2) + 1
        conn_kwargs = self.redis.connection_pool.connection_kwargs
        conn_kwargs['readonly'] = True
        for node in slave_nodes:
            try:
                # todo: a little bit dirty here, try to reuse StrictRedis later
                # todo: it may be optimized by using a new connection pool

                conn = ClusterConnection(host=node['host'], port=node['port'], **conn_kwargs)