Merge branch 'master' of https://github.com/nrodrigues/jedis into nrodrigues-master
This commit is contained in:
16
Makefile
16
Makefile
@@ -116,6 +116,18 @@ pidfile /tmp/sentinel3.pid
|
|||||||
logfile /tmp/sentinel3.log
|
logfile /tmp/sentinel3.log
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
define REDIS_SENTINEL4
|
||||||
|
port 26382
|
||||||
|
daemonize yes
|
||||||
|
sentinel monitor mymaster 127.0.0.1 6381 1
|
||||||
|
sentinel auth-pass mymaster foobared
|
||||||
|
sentinel down-after-milliseconds mymaster 2000
|
||||||
|
sentinel parallel-syncs mymaster 1
|
||||||
|
sentinel failover-timeout mymaster 120000
|
||||||
|
pidfile /tmp/sentinel4.pid
|
||||||
|
logfile /tmp/sentinel4.log
|
||||||
|
endef
|
||||||
|
|
||||||
# CLUSTER REDIS NODES
|
# CLUSTER REDIS NODES
|
||||||
define REDIS_CLUSTER_NODE1_CONF
|
define REDIS_CLUSTER_NODE1_CONF
|
||||||
daemonize yes
|
daemonize yes
|
||||||
@@ -199,6 +211,7 @@ export REDIS7_CONF
|
|||||||
export REDIS_SENTINEL1
|
export REDIS_SENTINEL1
|
||||||
export REDIS_SENTINEL2
|
export REDIS_SENTINEL2
|
||||||
export REDIS_SENTINEL3
|
export REDIS_SENTINEL3
|
||||||
|
export REDIS_SENTINEL4
|
||||||
export REDIS_CLUSTER_NODE1_CONF
|
export REDIS_CLUSTER_NODE1_CONF
|
||||||
export REDIS_CLUSTER_NODE2_CONF
|
export REDIS_CLUSTER_NODE2_CONF
|
||||||
export REDIS_CLUSTER_NODE3_CONF
|
export REDIS_CLUSTER_NODE3_CONF
|
||||||
@@ -219,6 +232,8 @@ start: cleanup
|
|||||||
echo "$$REDIS_SENTINEL2" > /tmp/sentinel2.conf && redis-server /tmp/sentinel2.conf --sentinel
|
echo "$$REDIS_SENTINEL2" > /tmp/sentinel2.conf && redis-server /tmp/sentinel2.conf --sentinel
|
||||||
@sleep 0.5
|
@sleep 0.5
|
||||||
echo "$$REDIS_SENTINEL3" > /tmp/sentinel3.conf && redis-server /tmp/sentinel3.conf --sentinel
|
echo "$$REDIS_SENTINEL3" > /tmp/sentinel3.conf && redis-server /tmp/sentinel3.conf --sentinel
|
||||||
|
@sleep 0.5
|
||||||
|
echo "$$REDIS_SENTINEL4" > /tmp/sentinel4.conf && redis-server /tmp/sentinel4.conf --sentinel
|
||||||
echo "$$REDIS_CLUSTER_NODE1_CONF" | redis-server -
|
echo "$$REDIS_CLUSTER_NODE1_CONF" | redis-server -
|
||||||
echo "$$REDIS_CLUSTER_NODE2_CONF" | redis-server -
|
echo "$$REDIS_CLUSTER_NODE2_CONF" | redis-server -
|
||||||
echo "$$REDIS_CLUSTER_NODE3_CONF" | redis-server -
|
echo "$$REDIS_CLUSTER_NODE3_CONF" | redis-server -
|
||||||
@@ -241,6 +256,7 @@ stop:
|
|||||||
kill `cat /tmp/sentinel1.pid`
|
kill `cat /tmp/sentinel1.pid`
|
||||||
kill `cat /tmp/sentinel2.pid`
|
kill `cat /tmp/sentinel2.pid`
|
||||||
kill `cat /tmp/sentinel3.pid`
|
kill `cat /tmp/sentinel3.pid`
|
||||||
|
kill `cat /tmp/sentinel4.pid`
|
||||||
kill `cat /tmp/redis_cluster_node1.pid` || true
|
kill `cat /tmp/redis_cluster_node1.pid` || true
|
||||||
kill `cat /tmp/redis_cluster_node2.pid` || true
|
kill `cat /tmp/redis_cluster_node2.pid` || true
|
||||||
kill `cat /tmp/redis_cluster_node3.pid` || true
|
kill `cat /tmp/redis_cluster_node3.pid` || true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package redis.clients.jedis;
|
package redis.clients.jedis;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.commons.pool2.PooledObject;
|
import org.apache.commons.pool2.PooledObject;
|
||||||
import org.apache.commons.pool2.PooledObjectFactory;
|
import org.apache.commons.pool2.PooledObjectFactory;
|
||||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||||
@@ -8,8 +10,7 @@ import org.apache.commons.pool2.impl.DefaultPooledObject;
|
|||||||
* PoolableObjectFactory custom impl.
|
* PoolableObjectFactory custom impl.
|
||||||
*/
|
*/
|
||||||
class JedisFactory implements PooledObjectFactory<Jedis> {
|
class JedisFactory implements PooledObjectFactory<Jedis> {
|
||||||
private final String host;
|
private final AtomicReference<HostAndPort> hostAndPort = new AtomicReference<HostAndPort>();
|
||||||
private final int port;
|
|
||||||
private final int timeout;
|
private final int timeout;
|
||||||
private final String password;
|
private final String password;
|
||||||
private final int database;
|
private final int database;
|
||||||
@@ -23,14 +24,17 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
|
|||||||
public JedisFactory(final String host, final int port, final int timeout,
|
public JedisFactory(final String host, final int port, final int timeout,
|
||||||
final String password, final int database, final String clientName) {
|
final String password, final int database, final String clientName) {
|
||||||
super();
|
super();
|
||||||
this.host = host;
|
this.hostAndPort.set(new HostAndPort(host, port));
|
||||||
this.port = port;
|
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.database = database;
|
this.database = database;
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setHostAndPort(final HostAndPort hostAndPort) {
|
||||||
|
this.hostAndPort.set(hostAndPort);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateObject(PooledObject<Jedis> pooledJedis)
|
public void activateObject(PooledObject<Jedis> pooledJedis)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -60,7 +64,8 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PooledObject<Jedis> makeObject() throws Exception {
|
public PooledObject<Jedis> makeObject() throws Exception {
|
||||||
final Jedis jedis = new Jedis(this.host, this.port, this.timeout);
|
final HostAndPort hostAndPort = this.hostAndPort.get();
|
||||||
|
final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), this.timeout);
|
||||||
|
|
||||||
jedis.connect();
|
jedis.connect();
|
||||||
if (null != this.password) {
|
if (null != this.password) {
|
||||||
@@ -86,7 +91,13 @@ class JedisFactory implements PooledObjectFactory<Jedis> {
|
|||||||
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
|
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
|
||||||
final BinaryJedis jedis = pooledJedis.getObject();
|
final BinaryJedis jedis = pooledJedis.getObject();
|
||||||
try {
|
try {
|
||||||
return jedis.isConnected() && jedis.ping().equals("PONG");
|
HostAndPort hostAndPort = this.hostAndPort.get();
|
||||||
|
|
||||||
|
String connectionHost = jedis.getClient().getHost();
|
||||||
|
int connectionPort = jedis.getClient().getPort();
|
||||||
|
|
||||||
|
return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort &&
|
||||||
|
jedis.isConnected() && jedis.ping().equals("PONG");
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
public JedisSentinelPool(String masterName, Set<String> sentinels,
|
public JedisSentinelPool(String masterName, Set<String> sentinels,
|
||||||
final GenericObjectPoolConfig poolConfig, int timeout,
|
final GenericObjectPoolConfig poolConfig, int timeout,
|
||||||
final String password, final int database) {
|
final String password, final int database) {
|
||||||
|
|
||||||
this.poolConfig = poolConfig;
|
this.poolConfig = poolConfig;
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
@@ -74,6 +75,7 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
initPool(master);
|
initPool(master);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private volatile JedisFactory factory;
|
||||||
private volatile HostAndPort currentHostMaster;
|
private volatile HostAndPort currentHostMaster;
|
||||||
|
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
@@ -91,10 +93,18 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
private void initPool(HostAndPort master) {
|
private void initPool(HostAndPort master) {
|
||||||
if (!master.equals(currentHostMaster)) {
|
if (!master.equals(currentHostMaster)) {
|
||||||
currentHostMaster = master;
|
currentHostMaster = master;
|
||||||
|
if (factory == null) {
|
||||||
|
factory = new JedisFactory(master.getHost(), master.getPort(),
|
||||||
|
timeout, password, database);
|
||||||
|
initPool(poolConfig, factory);
|
||||||
|
} else {
|
||||||
|
factory.setHostAndPort(currentHostMaster);
|
||||||
|
// although we clear the pool, we still have to check the returned object
|
||||||
|
// in getResource, this call only clears idle instances, not borrowed instances
|
||||||
|
internalPool.clear();
|
||||||
|
}
|
||||||
|
|
||||||
log.info("Created JedisPool to master at " + master);
|
log.info("Created JedisPool to master at " + master);
|
||||||
initPool(poolConfig,
|
|
||||||
new JedisFactory(master.getHost(), master.getPort(),
|
|
||||||
timeout, password, database));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,19 +125,23 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
|
|
||||||
log.fine("Connecting to Sentinel " + hap);
|
log.fine("Connecting to Sentinel " + hap);
|
||||||
|
|
||||||
|
Jedis jedis = null;
|
||||||
try {
|
try {
|
||||||
Jedis jedis = new Jedis(hap.getHost(), hap.getPort());
|
jedis = new Jedis(hap.getHost(), hap.getPort());
|
||||||
|
|
||||||
if (master == null) {
|
if (master == null) {
|
||||||
master = toHostAndPort(jedis
|
master = toHostAndPort(jedis
|
||||||
.sentinelGetMasterAddrByName(masterName));
|
.sentinelGetMasterAddrByName(masterName));
|
||||||
log.fine("Found Redis master at " + master);
|
log.fine("Found Redis master at " + master);
|
||||||
jedis.disconnect();
|
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
} catch (JedisConnectionException e) {
|
} catch (JedisConnectionException e) {
|
||||||
log.warning("Cannot connect to sentinel running @ " + hap
|
log.warning("Cannot connect to sentinel running @ " + hap
|
||||||
+ ". Trying next one.");
|
+ ". Trying next one.");
|
||||||
|
} finally {
|
||||||
|
if (jedis != null) {
|
||||||
|
jedis.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,9 +178,22 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Jedis getResource() {
|
public Jedis getResource() {
|
||||||
Jedis jedis = super.getResource();
|
while (true) {
|
||||||
jedis.setDataSource(this);
|
Jedis jedis = super.getResource();
|
||||||
return jedis;
|
jedis.setDataSource(this);
|
||||||
|
|
||||||
|
// get a reference because it can change concurrently
|
||||||
|
final HostAndPort master = currentHostMaster;
|
||||||
|
final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(),
|
||||||
|
jedis.getClient().getPort());
|
||||||
|
|
||||||
|
if (master.equals(connection)) {
|
||||||
|
// connected to the correct master
|
||||||
|
return jedis;
|
||||||
|
} else {
|
||||||
|
returnBrokenResource(jedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void returnBrokenResource(final Jedis resource) {
|
public void returnBrokenResource(final Jedis resource) {
|
||||||
@@ -304,4 +331,4 @@ public class JedisSentinelPool extends Pool<Jedis> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class HostAndPortUtil {
|
|||||||
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT));
|
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT));
|
||||||
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 1));
|
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 1));
|
||||||
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 2));
|
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 2));
|
||||||
|
sentinelHostAndPortList.add(new HostAndPort("localhost", Protocol.DEFAULT_SENTINEL_PORT + 3));
|
||||||
|
|
||||||
clusterHostAndPortList.add(new HostAndPort("localhost", 7379));
|
clusterHostAndPortList.add(new HostAndPort("localhost", 7379));
|
||||||
clusterHostAndPortList.add(new HostAndPort("localhost", 7380));
|
clusterHostAndPortList.add(new HostAndPort("localhost", 7380));
|
||||||
|
|||||||
@@ -21,18 +21,24 @@ public class JedisSentinelPoolTest extends JedisTestBase {
|
|||||||
.get(2);
|
.get(2);
|
||||||
protected static HostAndPort slave1 = HostAndPortUtil.getRedisServers()
|
protected static HostAndPort slave1 = HostAndPortUtil.getRedisServers()
|
||||||
.get(3);
|
.get(3);
|
||||||
|
|
||||||
protected static HostAndPort sentinel1 = HostAndPortUtil
|
protected static HostAndPort sentinel1 = HostAndPortUtil
|
||||||
.getSentinelServers().get(1);
|
.getSentinelServers().get(1);
|
||||||
|
protected static HostAndPort sentinel2 = HostAndPortUtil
|
||||||
|
.getSentinelServers().get(3);
|
||||||
|
|
||||||
protected static Jedis sentinelJedis1;
|
protected static Jedis sentinelJedis1;
|
||||||
|
protected static Jedis sentinelJedis2;
|
||||||
|
|
||||||
protected Set<String> sentinels = new HashSet<String>();
|
protected Set<String> sentinels = new HashSet<String>();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
sentinels.add(sentinel1.toString());
|
sentinels.add(sentinel1.toString());
|
||||||
|
sentinels.add(sentinel2.toString());
|
||||||
|
|
||||||
sentinelJedis1 = new Jedis(sentinel1.getHost(), sentinel1.getPort());
|
sentinelJedis1 = new Jedis(sentinel1.getHost(), sentinel1.getPort());
|
||||||
|
sentinelJedis2 = new Jedis(sentinel2.getHost(), sentinel2.getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -41,6 +47,8 @@ public class JedisSentinelPoolTest extends JedisTestBase {
|
|||||||
new GenericObjectPoolConfig(), 1000, "foobared", 2);
|
new GenericObjectPoolConfig(), 1000, "foobared", 2);
|
||||||
|
|
||||||
forceFailover(pool);
|
forceFailover(pool);
|
||||||
|
// after failover sentinel needs a bit of time to stabilize before a new failover
|
||||||
|
Thread.sleep(100);
|
||||||
forceFailover(pool);
|
forceFailover(pool);
|
||||||
|
|
||||||
// you can test failover as much as possible
|
// you can test failover as much as possible
|
||||||
@@ -134,29 +142,25 @@ public class JedisSentinelPoolTest extends JedisTestBase {
|
|||||||
HostAndPort oldMaster = pool.getCurrentHostMaster();
|
HostAndPort oldMaster = pool.getCurrentHostMaster();
|
||||||
|
|
||||||
// jedis connection should be master
|
// jedis connection should be master
|
||||||
Jedis jedis = pool.getResource();
|
Jedis beforeFailoverJedis = pool.getResource();
|
||||||
assertEquals("PONG", jedis.ping());
|
assertEquals("PONG", beforeFailoverJedis.ping());
|
||||||
|
|
||||||
// It can throw JedisDataException while there's no slave to promote
|
|
||||||
// There's nothing we can do, so we just pass Exception to make test
|
|
||||||
// fail fast
|
|
||||||
sentinelJedis1.sentinelFailover(MASTER_NAME);
|
|
||||||
|
|
||||||
waitForFailover(pool, oldMaster);
|
waitForFailover(pool, oldMaster);
|
||||||
// JedisSentinelPool recognize master but may not changed internal pool
|
|
||||||
// yet
|
Jedis afterFailoverJedis = pool.getResource();
|
||||||
Thread.sleep(100);
|
assertEquals("PONG", afterFailoverJedis.ping());
|
||||||
|
assertEquals("foobared", afterFailoverJedis.configGet("requirepass").get(1));
|
||||||
|
assertEquals(2, afterFailoverJedis.getDB().intValue());
|
||||||
|
|
||||||
jedis = pool.getResource();
|
// returning both connections to the pool should not throw
|
||||||
assertEquals("PONG", jedis.ping());
|
beforeFailoverJedis.close();
|
||||||
assertEquals("foobared", jedis.configGet("requirepass").get(1));
|
afterFailoverJedis.close();
|
||||||
assertEquals(2, jedis.getDB().intValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForFailover(JedisSentinelPool pool, HostAndPort oldMaster)
|
private void waitForFailover(JedisSentinelPool pool, HostAndPort oldMaster)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
HostAndPort newMaster = JedisSentinelTestUtil
|
HostAndPort newMaster = JedisSentinelTestUtil
|
||||||
.waitForNewPromotedMaster(sentinelJedis1);
|
.waitForNewPromotedMaster(MASTER_NAME, sentinelJedis1, sentinelJedis2);
|
||||||
|
|
||||||
waitForJedisSentinelPoolRecognizeNewMaster(pool, newMaster);
|
waitForJedisSentinelPoolRecognizeNewMaster(pool, newMaster);
|
||||||
}
|
}
|
||||||
@@ -166,10 +170,9 @@ public class JedisSentinelPoolTest extends JedisTestBase {
|
|||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
String host = pool.getCurrentHostMaster().getHost();
|
HostAndPort currentHostMaster = pool.getCurrentHostMaster();
|
||||||
int port = pool.getCurrentHostMaster().getPort();
|
|
||||||
|
|
||||||
if (host.equals(newMaster.getHost()) && port == newMaster.getPort())
|
if (newMaster.equals(currentHostMaster))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
System.out
|
System.out
|
||||||
|
|||||||
@@ -85,16 +85,16 @@ public class JedisSentinelTest extends JedisTestBase {
|
|||||||
public void sentinelFailover() throws InterruptedException {
|
public void sentinelFailover() throws InterruptedException {
|
||||||
Jedis j = new Jedis(sentinelForFailover.getHost(),
|
Jedis j = new Jedis(sentinelForFailover.getHost(),
|
||||||
sentinelForFailover.getPort());
|
sentinelForFailover.getPort());
|
||||||
|
Jedis j2 = new Jedis(sentinelForFailover.getHost(),
|
||||||
|
sentinelForFailover.getPort());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<String> masterHostAndPort = j
|
List<String> masterHostAndPort = j
|
||||||
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
|
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
|
||||||
HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0),
|
HostAndPort currentMaster = new HostAndPort(masterHostAndPort.get(0),
|
||||||
Integer.parseInt(masterHostAndPort.get(1)));
|
Integer.parseInt(masterHostAndPort.get(1)));
|
||||||
String result = j.sentinelFailover(FAILOVER_MASTER_NAME);
|
|
||||||
assertEquals("OK", result);
|
|
||||||
|
|
||||||
JedisSentinelTestUtil.waitForNewPromotedMaster(j);
|
JedisSentinelTestUtil.waitForNewPromotedMaster(FAILOVER_MASTER_NAME, j, j2);
|
||||||
|
|
||||||
masterHostAndPort = j
|
masterHostAndPort = j
|
||||||
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
|
.sentinelGetMasterAddrByName(FAILOVER_MASTER_NAME);
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import redis.clients.jedis.JedisPubSub;
|
|||||||
import redis.clients.jedis.tests.utils.FailoverAbortedException;
|
import redis.clients.jedis.tests.utils.FailoverAbortedException;
|
||||||
|
|
||||||
public class JedisSentinelTestUtil {
|
public class JedisSentinelTestUtil {
|
||||||
public static HostAndPort waitForNewPromotedMaster(Jedis sentinelJedis)
|
public static HostAndPort waitForNewPromotedMaster(final String masterName,
|
||||||
|
final Jedis sentinelJedis, final Jedis commandJedis)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
|
|
||||||
final AtomicReference<String> newmaster = new AtomicReference<String>(
|
final AtomicReference<String> newmaster = new AtomicReference<String>(
|
||||||
@@ -47,6 +48,7 @@ public class JedisSentinelTestUtil {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPSubscribe(String pattern, int subscribedChannels) {
|
public void onPSubscribe(String pattern, int subscribedChannels) {
|
||||||
|
commandJedis.sentinelFailover(masterName);
|
||||||
}
|
}
|
||||||
}, "*");
|
}, "*");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user