diff --git a/modules/clients/pom.xml b/modules/clients/pom.xml index 578c898d9c3e3..649e79c28e915 100644 --- a/modules/clients/pom.xml +++ b/modules/clients/pom.xml @@ -50,7 +50,7 @@ redis.clients jedis - 2.9.0 + 8.0.1 test diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolConnectSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolConnectSelfTest.java index b22f823042fb6..16c0e76ee8030 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolConnectSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolConnectSelfTest.java @@ -18,9 +18,11 @@ package org.apache.ignite.internal.processors.rest.protocols.tcp.redis; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.ignite.testframework.GridTestUtils; import org.junit.Assert; import org.junit.Test; import redis.clients.jedis.Jedis; +import redis.clients.jedis.exceptions.JedisDataException; import static org.apache.ignite.internal.util.IgniteUtils.KB; @@ -28,31 +30,25 @@ * Tests for Connection commands of Redis protocol. */ public class RedisProtocolConnectSelfTest extends RedisCommonAbstractTest { - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testPing() throws Exception { + public void testPing() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals("PONG", jedis.ping()); } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testEcho() throws Exception { + public void testEcho() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals("Hello, grid!", jedis.echo("Hello, grid!")); } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testSelect() throws Exception { + public void testSelect() { try (Jedis jedis = pool.getResource()) { // connected to cache with index 0 jedis.set("k0", "v0"); @@ -78,6 +74,36 @@ public void testSelect() throws Exception { } } + /** */ + @Test + public void testClient() { + try (Jedis jedis = pool.getResource()) { + Assert.assertNull(jedis.clientGetname()); + + Assert.assertEquals("OK", jedis.clientSetname("test-client")); + Assert.assertEquals("test-client", jedis.clientGetname()); + + // The name is connection-scoped. + try (Jedis jedis2 = pool.getResource()) { + Assert.assertNull(jedis2.clientGetname()); + } + + Assert.assertEquals("test-client", jedis.clientGetname()); + } + } + + /** */ + @Test + public void testClientUnknownSubcommand() { + try (Jedis jedis = pool.getResource()) { + GridTestUtils.assertThrows(log, () -> jedis.clientUnpause(), JedisDataException.class, + "Unknown subcommand 'UNPAUSE' for 'client' command"); + + // The connection is still usable. + Assert.assertEquals("PONG", jedis.ping()); + } + } + /** */ @Test public void testSetGetLongString() { diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolStringSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolStringSelfTest.java index c1d5e08416fa6..90f22820a59cd 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolStringSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/RedisProtocolStringSelfTest.java @@ -26,16 +26,15 @@ import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisDataException; +import redis.clients.jedis.params.SetParams; /** * Tests for String commands of Redis protocol. */ public class RedisProtocolStringSelfTest extends RedisCommonAbstractTest { - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testGet() throws Exception { + public void testGet() { try (Jedis jedis = pool.getResource()) { jcache().put("getKey1", "getVal1"); @@ -55,11 +54,9 @@ public void testGet() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testGetSet() throws Exception { + public void testGetSet() { try (Jedis jedis = pool.getResource()) { jcache().put("getSetKey1", "1"); @@ -79,11 +76,9 @@ public void testGetSet() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testMGet() throws Exception { + public void testMGet() { try (Jedis jedis = pool.getResource()) { jcache().put("getKey1", "getVal1"); jcache().put("getKey2", 0); @@ -99,19 +94,15 @@ public void testMGet() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testMGetDirectOrder() throws Exception { + public void testMGetDirectOrder() { testMGetOrder(true); } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testMGetReverseOrder() throws Exception { + public void testMGetReverseOrder() { testMGetOrder(false); } @@ -153,11 +144,9 @@ public void testMGetOrder(boolean directOrder) { } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testMGetDuplicates() throws Exception { + public void testMGetDuplicates() { try (Jedis jedis = pool.getResource()) { jcache().put("key-A", "value-A"); jcache().put("key-B", "value-B"); @@ -188,14 +177,14 @@ public void testSet() throws Exception { Assert.assertEquals("b0", jcache().get("setKey2")); // test options. - jedis.set("setKey1", "2", "nx"); - jedis.set("setKey3", "3", "nx", "px", EXPIRE_MS); + jedis.set("setKey1", "2", SetParams.setParams().nx()); + jedis.set("setKey3", "3", SetParams.setParams().nx().px(EXPIRE_MS)); Assert.assertEquals("1", jcache().get("setKey1")); Assert.assertEquals("3", jcache().get("setKey3")); - jedis.set("setKey1", "2", "xx", "ex", EXPIRE_SEC); - jedis.set("setKey4", "4", "xx"); + jedis.set("setKey1", "2", SetParams.setParams().xx().ex(EXPIRE_SEC)); + jedis.set("setKey4", "4", SetParams.setParams().xx()); Assert.assertEquals("2", jcache().get("setKey1")); Assert.assertNull(jcache().get("setKey4")); @@ -208,11 +197,9 @@ public void testSet() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testMSet() throws Exception { + public void testMSet() { try (Jedis jedis = pool.getResource()) { jedis.mset("setKey1", "1", "setKey2", "2"); @@ -221,11 +208,9 @@ public void testMSet() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testIncrDecr() throws Exception { + public void testIncrDecr() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals(1, (long)jedis.incr("newKeyIncr")); Assert.assertEquals(-1, (long)jedis.decr("newKeyDecr")); @@ -305,11 +290,9 @@ public void testIncrDecr() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testIncrDecrBy() throws Exception { + public void testIncrDecrBy() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals(2, (long)jedis.incrBy("newKeyIncrBy", 2)); Assert.assertEquals(-2, (long)jedis.decrBy("newKeyDecrBy", 2)); @@ -362,11 +345,9 @@ public void testIncrDecrBy() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testAppend() throws Exception { + public void testAppend() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals(5, (long)jedis.append("appendKey1", "Hello")); Assert.assertEquals(12, (long)jedis.append("appendKey1", " World!")); @@ -384,11 +365,9 @@ public void testAppend() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testStrlen() throws Exception { + public void testStrlen() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals(0, (long)jedis.strlen("strlenKeyNonExisting")); @@ -409,11 +388,9 @@ public void testStrlen() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testSetRange() throws Exception { + public void testSetRange() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals(0, (long)jedis.setrange("setRangeKey1", 0, "")); @@ -458,11 +435,9 @@ public void testSetRange() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testGetRange() throws Exception { + public void testGetRange() { try (Jedis jedis = pool.getResource()) { Assert.assertEquals("", jedis.getrange("getRangeKeyNonExisting", 0, 0)); @@ -486,11 +461,9 @@ public void testGetRange() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testDel() throws Exception { + public void testDel() { jcache().put("delKey1", "abc"); jcache().put("delKey2", "abcd"); try (Jedis jedis = pool.getResource()) { @@ -500,11 +473,9 @@ public void testDel() throws Exception { } } - /** - * @throws Exception If failed. - */ + /** */ @Test - public void testExists() throws Exception { + public void testExists() { jcache().put("existsKey1", "abc"); jcache().put("existsKey2", "abcd"); try (Jedis jedis = pool.getResource()) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java index 368a2c3b767db..032026e04ee59 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestCommand.java @@ -223,7 +223,10 @@ public enum GridRestCommand { WARM_UP("warmup"), /** probe. */ - PROBE("probe"); + PROBE("probe"), + + /** Client info. */ + CLIENT("client"); /** Enum values. */ private static final GridRestCommand[] VALS = values(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java index e417ede2f6e99..f7bd2dbf30c13 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java @@ -57,6 +57,7 @@ import org.apache.ignite.internal.processors.rest.handlers.memory.MemoryMetricsCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.probe.GridProbeCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.query.QueryCommandHandler; +import org.apache.ignite.internal.processors.rest.handlers.server.GridClientInfoCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.task.GridTaskCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.top.GridTopologyCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.user.UserActionCommandHandler; @@ -557,6 +558,7 @@ public GridRestProcessor(GridKernalContext ctx) { addHandler(new MemoryMetricsCommandHandler(ctx)); addHandler(new NodeStateBeforeStartCommandHandler(ctx)); addHandler(new GridProbeCommandHandler(ctx)); + addHandler(new GridClientInfoCommandHandler(ctx)); // Start protocols. startTcpProtocol(); @@ -955,6 +957,7 @@ private void authorize(GridRestRequest req) throws SecurityException { case REMOVE_USER: case UPDATE_USER: case PROBE: + case CLIENT: break; default: diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/redis/server/GridRedisClientCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/redis/server/GridRedisClientCommandHandler.java new file mode 100644 index 0000000000000..e9b14dd5b6256 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/redis/server/GridRedisClientCommandHandler.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.rest.handlers.redis.server; + +import java.util.Collection; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.processors.rest.handlers.redis.GridRedisCommandHandler; +import org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisCommand; +import org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisMessage; +import org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisProtocolParser; +import org.apache.ignite.internal.util.future.GridFinishedFuture; +import org.apache.ignite.internal.util.nio.GridNioSession; +import org.apache.ignite.internal.util.nio.GridNioSessionMetaKey; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; + +import static org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisCommand.CLIENT; + +/** + * Redis CLIENT command handler. + *

+ * CLIENT is a connection-scoped command container, so it is handled locally, without a REST round trip. + * Only the subcommands that carry no cluster-wide state are supported, the rest are answered with an error. + */ +public class GridRedisClientCommandHandler implements GridRedisCommandHandler { + /** Supported commands. */ + private static final Collection SUPPORTED_COMMANDS = U.sealList(CLIENT); + + /** Session metadata key for the name set by CLIENT SETNAME. */ + private static final int CLIENT_NAME_META_KEY = GridNioSessionMetaKey.nextUniqueKey(); + + /** Position of the first argument of a CLIENT subcommand. */ + private static final int ARG_POS = 2; + + /** {@inheritDoc} */ + @Override public Collection supportedCommands() { + return SUPPORTED_COMMANDS; + } + + /** {@inheritDoc} */ + @Override public IgniteInternalFuture handleAsync(GridNioSession ses, GridRedisMessage msg) { + assert msg != null; + + String subCmd = msg.key(); + + if (F.isEmpty(subCmd)) { + msg.setResponse(GridRedisProtocolParser.toGenericError( + "wrong number of arguments for 'client' command")); + + return new GridFinishedFuture<>(msg); + } + + switch (subCmd.toUpperCase()) { + case "SETNAME": { + String name = msg.aux(ARG_POS); + + if (name == null || name.indexOf(' ') >= 0 || name.indexOf('\n') >= 0) + msg.setResponse(GridRedisProtocolParser.toGenericError( + "Client names cannot contain spaces, newlines or special characters.")); + else { + ses.addMeta(CLIENT_NAME_META_KEY, name); + + msg.setResponse(GridRedisProtocolParser.oKString()); + } + + break; + } + + case "GETNAME": { + String name = ses.meta(CLIENT_NAME_META_KEY); + + msg.setResponse(name == null + ? GridRedisProtocolParser.nil() + : GridRedisProtocolParser.toBulkString(name)); + + break; + } + + case "SETINFO": + // The library name and version announced by a driver: accepted and ignored. + msg.setResponse(GridRedisProtocolParser.oKString()); + + break; + + default: + msg.setResponse(GridRedisProtocolParser.toGenericError( + "Unknown subcommand '" + subCmd + "' for 'client' command")); + } + + return new GridFinishedFuture<>(msg); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/server/GridClientInfoCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/server/GridClientInfoCommandHandler.java new file mode 100644 index 0000000000000..bd7ef382bb968 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/server/GridClientInfoCommandHandler.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.rest.handlers.server; + +import java.util.Collection; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.processors.rest.GridRestCommand; +import org.apache.ignite.internal.processors.rest.GridRestResponse; +import org.apache.ignite.internal.processors.rest.handlers.GridRestCommandHandlerAdapter; +import org.apache.ignite.internal.processors.rest.request.GridRestRequest; +import org.apache.ignite.internal.util.future.GridFinishedFuture; +import org.apache.ignite.internal.util.typedef.internal.U; + +import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLIENT; + +/** + * Command handler for CLIENT command (returns client info). + */ +public class GridClientInfoCommandHandler extends GridRestCommandHandlerAdapter { + /** Supported commands. */ + private static final Collection SUPPORTED_COMMANDS = U.sealList(CLIENT); + + /** + * @param ctx Context. + */ + public GridClientInfoCommandHandler(GridKernalContext ctx) { + super(ctx); + } + + /** {@inheritDoc} */ + @Override public Collection supportedCommands() { + return SUPPORTED_COMMANDS; + } + + /** {@inheritDoc} */ + @Override public IgniteInternalFuture handleAsync(GridRestRequest req) { + assert CLIENT == req.command(); + + GridRestResponse res = new GridRestResponse(); + + res.setResponse(0); + + return new GridFinishedFuture<>(res); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisCommand.java index bc32fb49ee4ae..e9c44dd00b7f6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisCommand.java @@ -78,7 +78,9 @@ public enum GridRedisCommand { /** FLUSHDB. */ FLUSHDB("FLUSHDB"), /** FLUSHALL. */ - FLUSHALL("FLUSHALL"); + FLUSHALL("FLUSHALL"), + /** CLIENT. */ + CLIENT("CLIENT"); /** String for command. */ private final String cmd; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisNioListener.java index 955eed9f2c8f8..4b6337955ba0c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisNioListener.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/redis/GridRedisNioListener.java @@ -29,6 +29,7 @@ import org.apache.ignite.internal.processors.rest.handlers.redis.key.GridRedisDelCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.redis.key.GridRedisExistsCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.redis.key.GridRedisExpireCommandHandler; +import org.apache.ignite.internal.processors.rest.handlers.redis.server.GridRedisClientCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.redis.server.GridRedisDbSizeCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.redis.server.GridRedisFlushCommandHandler; import org.apache.ignite.internal.processors.rest.handlers.redis.string.GridRedisAppendCommandHandler; @@ -92,6 +93,7 @@ public GridRedisNioListener(IgniteLogger log, GridRestProtocolHandler hnd, GridK // server commands. addCommandHandler(new GridRedisDbSizeCommandHandler(log, hnd, ctx)); addCommandHandler(new GridRedisFlushCommandHandler(log, hnd, ctx)); + addCommandHandler(new GridRedisClientCommandHandler()); } /**