diff --git a/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java b/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java new file mode 100644 index 0000000000000..fe2408e6b5f52 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java @@ -0,0 +1,56 @@ +/* + * 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.client; + +/** + * Thin client services facade. + */ +public interface ClientServices { + /** + * Gets the cluster group to which this {@code ClientServices} instance belongs. + * + * @return Cluster group to which this {@code ClientServices} instance belongs. + */ + public ClientClusterGroup clusterGroup(); + + /** + * Gets a remote handle on the service. + *

+ * Note: There are no guarantees that each method invocation for the same proxy will always contact the same remote + * service (on the same remote node). + * + * @param name Service name. + * @param svcItf Interface for the service. + * @return Proxy over remote service. + */ + public T serviceProxy(String name, Class svcItf); + + /** + * Gets a remote handle on the service with timeout. + *

+ * Note: There are no guarantees that each method invocation for the same proxy will always contact the same remote + * service (on the same remote node). + * + * @param name Service name. + * @param svcItf Interface for the service. + * @param timeout If greater than 0 created proxy will wait for service availability only specified time, + * and will limit remote service invocation time. + * @return Proxy over remote service. + */ + public T serviceProxy(String name, Class svcItf, long timeout); +} diff --git a/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java b/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java index fa8cf8f5f2ea4..1d10a8aeda8b1 100644 --- a/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java +++ b/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java @@ -119,4 +119,24 @@ public interface IgniteClient extends AutoCloseable { * @return Client cluster facade. */ public ClientCluster cluster(); + + /** + * Gets {@code services} facade over all cluster nodes started in server mode. + * + * @return Services facade over all cluster nodes started in server mode. + */ + public ClientServices services(); + + /** + * Gets {@code services} facade over nodes within the cluster group. All operations + * on the returned {@link ClientServices} instance will only include nodes from + * the specified cluster group. + * + * Note: In some cases there will be additional requests for each service invocation from client to server + * to resolve cluster group. + * + * @param grp Cluster group. + * @return {@code Services} functionality over given cluster group. + */ + public ClientServices services(ClientClusterGroup grp); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java index 3cf228cff4d01..34fb17d560606 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java @@ -74,7 +74,9 @@ enum ClientOperation { /** Get nodes info by IDs. */CLUSTER_GROUP_GET_NODE_INFO(5101), /** Execute compute task. */COMPUTE_TASK_EXECUTE(6000), - /** Finished compute task notification. */COMPUTE_TASK_FINISHED(6001, true); + /** Finished compute task notification. */COMPUTE_TASK_FINISHED(6001, true), + + /** Invoke service. */SERVICE_INVOKE(7000); /** Code. */ private final int code; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java new file mode 100644 index 0000000000000..5bf523408c143 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java @@ -0,0 +1,180 @@ +/* + * 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.client.thin; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.UUID; +import org.apache.ignite.client.ClientClusterGroup; +import org.apache.ignite.client.ClientException; +import org.apache.ignite.client.ClientServices; +import org.apache.ignite.internal.binary.BinaryRawWriterEx; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.A; +import org.apache.ignite.platform.PlatformServiceMethod; + +/** + * Implementation of {@link ClientServices}. + */ +class ClientServicesImpl implements ClientServices { + /** Channel. */ + private final ReliableChannel ch; + + /** Binary marshaller. */ + private final ClientBinaryMarshaller marsh; + + /** Utils for serialization/deserialization. */ + private final ClientUtils utils; + + /** Cluster group. */ + private final ClientClusterGroupImpl grp; + + /** Constructor. */ + ClientServicesImpl(ReliableChannel ch, ClientBinaryMarshaller marsh, ClientClusterGroupImpl grp) { + this.ch = ch; + this.marsh = marsh; + this.grp = grp; + + utils = new ClientUtils(marsh); + } + + /** {@inheritDoc} */ + @Override public ClientClusterGroup clusterGroup() { + return grp; + } + + /** {@inheritDoc} */ + @Override public T serviceProxy(String name, Class svcItf) { + return serviceProxy(name, svcItf, 0); + } + + /** {@inheritDoc} */ + @Override public T serviceProxy(String name, Class svcItf, long timeout) { + A.notNullOrEmpty(name, "name"); + A.notNull(svcItf, "svcItf"); + + return (T)Proxy.newProxyInstance(svcItf.getClassLoader(), new Class[] {svcItf}, + new ServiceInvocationHandler<>(name, timeout, grp)); + } + + /** + * Gets services facade over the specified cluster group. + * + * @param grp Cluster group. + */ + ClientServices withClusterGroup(ClientClusterGroupImpl grp) { + A.notNull(grp, "grp"); + + return new ClientServicesImpl(ch, marsh, grp); + } + + /** + * Service invocation handler. + */ + private class ServiceInvocationHandler implements InvocationHandler { + /** Flag "Has parameter types" mask. */ + private static final byte FLAG_PARAMETER_TYPES_MASK = 0x02; + + /** Service name. */ + private final String name; + + /** Timeout. */ + private final long timeout; + + /** Cluster group. */ + private final ClientClusterGroupImpl grp; + + /** + * @param name Service name. + * @param timeout Timeout. + */ + private ServiceInvocationHandler(String name, long timeout, ClientClusterGroupImpl grp) { + this.name = name; + this.timeout = timeout; + this.grp = grp; + } + + /** {@inheritDoc} */ + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + Collection nodeIds = grp.nodeIds(); + + if (nodeIds != null && nodeIds.isEmpty()) + throw new ClientException("Cluster group is empty."); + + return ch.service(ClientOperation.SERVICE_INVOKE, + req -> writeServiceInvokeRequest(req, nodeIds, method, args), + res -> utils.readObject(res.in(), false) + ); + } + catch (ClientError e) { + throw new ClientException(e); + } + } + + /** + * @param ch Payload output channel. + */ + private void writeServiceInvokeRequest( + PayloadOutputChannel ch, + Collection nodeIds, + Method method, + Object[] args + ) { + ch.clientChannel().protocolCtx().checkFeatureSupported(ProtocolBitmaskFeature.SERVICE_INVOKE); + + try (BinaryRawWriterEx writer = utils.createBinaryWriter(ch.out())) { + writer.writeString(name); + writer.writeByte(FLAG_PARAMETER_TYPES_MASK); // Flags. + writer.writeLong(timeout); + + if (nodeIds == null) + writer.writeInt(0); + else { + writer.writeInt(nodeIds.size()); + + for (UUID nodeId : nodeIds) { + writer.writeLong(nodeId.getMostSignificantBits()); + writer.writeLong(nodeId.getLeastSignificantBits()); + } + } + + PlatformServiceMethod ann = method.getDeclaredAnnotation(PlatformServiceMethod.class); + + writer.writeString(ann != null ? ann.value() : method.getName()); + + Class[] paramTypes = method.getParameterTypes(); + + if (F.isEmpty(args)) + writer.writeInt(0); + else { + writer.writeInt(args.length); + + assert args.length == paramTypes.length : "args=" + args.length + ", types=" + paramTypes.length; + + for (int i = 0; i < args.length; i++) { + writer.writeInt(marsh.context().typeId(paramTypes[i].getName())); + writer.writeObject(args[i]); + } + } + } + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java index a781546ab3d6d..d59200f343d6b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.client.thin; import java.io.IOException; +import java.lang.reflect.Array; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; @@ -55,6 +56,7 @@ import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.BinaryReaderHandles; import org.apache.ignite.internal.binary.BinarySchema; +import org.apache.ignite.internal.binary.BinaryThreadLocalContext; import org.apache.ignite.internal.binary.BinaryUtils; import org.apache.ignite.internal.binary.BinaryWriterExImpl; import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream; @@ -527,6 +529,13 @@ void writeObject(BinaryOutputStream out, Object obj) { out.writeByteArray(marsh.marshal(obj)); } + /** + * @param out Output stream. + */ + BinaryRawWriterEx createBinaryWriter(BinaryOutputStream out) { + return new BinaryWriterExImpl(marsh.context(), out, BinaryThreadLocalContext.get().schemaHolder(), null); + } + /** Read Ignite binary object from input stream. */ T readObject(BinaryInputStream in, boolean keepBinary) { if (keepBinary) @@ -590,7 +599,7 @@ private Object[] unwrapArray(Object[] arr, BinaryReaderHandles hnds) { if (BinaryUtils.knownArray(arr)) return arr; - Object[] res = new Object[arr.length]; + Object[] res = (Object[])Array.newInstance(arr.getClass().getComponentType(), arr.length); for (int i = 0; i < arr.length; i++) res[i] = unwrapBinary(arr[i], hnds); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java index 3b705b9637fae..c86dd7b181fe8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java @@ -37,7 +37,10 @@ public enum ProtocolBitmaskFeature { CLUSTER_STATES(2), /** Cluster groups. */ - CLUSTER_GROUPS(4); + CLUSTER_GROUPS(4), + + /** Invoke service methods. */ + SERVICE_INVOKE(5); /** */ private static final EnumSet ALL_FEATURES_AS_ENUM_SET = diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java index e5be57582feef..ec8fb6e2f51bf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.client.thin; import java.util.EnumSet; +import org.apache.ignite.client.ClientFeatureNotSupportedByServerException; /** * Protocol Context. @@ -45,6 +46,17 @@ public boolean isFeatureSupported(ProtocolBitmaskFeature feature) { return features.contains(feature); } + /** + * Check that feature is supported by the server. + * + * @param feature Feature. + * @throws ClientFeatureNotSupportedByServerException If feature is not supported by the server. + */ + public void checkFeatureSupported(ProtocolBitmaskFeature feature) throws ClientFeatureNotSupportedByServerException { + if (!isFeatureSupported(feature)) + throw new ClientFeatureNotSupportedByServerException(feature); + } + /** * @return {@code true} if protocol version feature supported. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java index e4c6b9fcdab5c..3db66f5bf8e3e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java @@ -38,6 +38,7 @@ import org.apache.ignite.client.ClientClusterGroup; import org.apache.ignite.client.ClientCompute; import org.apache.ignite.client.ClientException; +import org.apache.ignite.client.ClientServices; import org.apache.ignite.client.ClientTransactions; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.configuration.ClientConfiguration; @@ -78,6 +79,9 @@ public class TcpIgniteClient implements IgniteClient { /** Cluster facade. */ private final ClientClusterImpl cluster; + /** Services facade. */ + private final ClientServicesImpl services; + /** Marshaller. */ private final ClientBinaryMarshaller marsh; @@ -115,6 +119,8 @@ private TcpIgniteClient(ClientConfiguration cfg) throws ClientException { cluster = new ClientClusterImpl(ch, marsh); compute = new ClientComputeImpl(ch, marsh, cluster.defaultClusterGroup()); + + services = new ClientServicesImpl(ch, marsh, cluster.defaultClusterGroup()); } /** {@inheritDoc} */ @@ -228,6 +234,16 @@ private TcpIgniteClient(ClientConfiguration cfg) throws ClientException { return cluster; } + /** {@inheritDoc} */ + @Override public ClientServices services() { + return services; + } + + /** {@inheritDoc} */ + @Override public ClientServices services(ClientClusterGroup grp) { + return services.withClusterGroup((ClientClusterGroupImpl)grp); + } + /** * Initializes new instance of {@link IgniteClient}. * diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java index 3da4e11587d47..3ec7530452ff1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java @@ -37,7 +37,10 @@ public enum ClientBitmaskFeature implements ThinProtocolFeature { CLUSTER_GROUP_GET_NODES_ENDPOINTS(3), /** Cluster groups. */ - CLUSTER_GROUPS(4); + CLUSTER_GROUPS(4), + + /** Service invocation. */ + SERVICE_INVOKE(5); /** */ private static final EnumSet ALL_FEATURES_AS_ENUM_SET = diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java index 37ef13123e980..94d32bc53be71 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java @@ -17,7 +17,6 @@ package org.apache.ignite.internal.processors.platform.client; -import org.apache.ignite.internal.binary.BinaryRawReaderEx; import org.apache.ignite.internal.binary.BinaryRawWriterEx; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.GridBinaryMarshaller; @@ -75,6 +74,7 @@ import org.apache.ignite.internal.processors.platform.client.cluster.ClientClusterWalChangeStateRequest; import org.apache.ignite.internal.processors.platform.client.cluster.ClientClusterWalGetStateRequest; import org.apache.ignite.internal.processors.platform.client.compute.ClientExecuteTaskRequest; +import org.apache.ignite.internal.processors.platform.client.service.ClientServiceInvokeRequest; import org.apache.ignite.internal.processors.platform.client.tx.ClientTxEndRequest; import org.apache.ignite.internal.processors.platform.client.tx.ClientTxStartRequest; @@ -250,6 +250,9 @@ public class ClientMessageParser implements ClientListenerMessageParser { /** */ public static final short OP_COMPUTE_TASK_FINISHED = 6001; + /** Service invocation. */ + private static final short OP_SERVICE_INVOKE = 7000; + /** Marshaller. */ private final GridBinaryMarshaller marsh; @@ -280,7 +283,7 @@ public class ClientMessageParser implements ClientListenerMessageParser { BinaryInputStream inStream = new BinaryHeapInputStream(msg); // skipHdrCheck must be true (we have 103 op code). - BinaryRawReaderEx reader = new BinaryReaderExImpl(marsh.context(), inStream, + BinaryReaderExImpl reader = new BinaryReaderExImpl(marsh.context(), inStream, null, null, true, true); return decode(reader); @@ -292,7 +295,7 @@ public class ClientMessageParser implements ClientListenerMessageParser { * @param reader Reader. * @return Request. */ - public ClientListenerRequest decode(BinaryRawReaderEx reader) { + public ClientListenerRequest decode(BinaryReaderExImpl reader) { short opCode = reader.readShort(); switch (opCode) { @@ -450,6 +453,9 @@ public ClientListenerRequest decode(BinaryRawReaderEx reader) { case OP_COMPUTE_TASK_EXECUTE: return new ClientExecuteTaskRequest(reader); + + case OP_SERVICE_INVOKE: + return new ClientServiceInvokeRequest(reader); } return new ClientRawRequest(reader.readLong(), ClientStatus.INVALID_OP_CODE, diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java new file mode 100644 index 0000000000000..e51780bea678a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java @@ -0,0 +1,323 @@ +/* + * 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.platform.client.service; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.ignite.IgniteBinary; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteServices; +import org.apache.ignite.internal.binary.BinaryRawReaderEx; +import org.apache.ignite.internal.binary.BinaryReaderExImpl; +import org.apache.ignite.internal.cluster.ClusterGroupAdapter; +import org.apache.ignite.internal.processors.platform.PlatformNativeException; +import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext; +import org.apache.ignite.internal.processors.platform.client.ClientObjectResponse; +import org.apache.ignite.internal.processors.platform.client.ClientRequest; +import org.apache.ignite.internal.processors.platform.client.ClientResponse; +import org.apache.ignite.internal.processors.platform.services.PlatformService; +import org.apache.ignite.internal.processors.platform.services.PlatformServices; +import org.apache.ignite.internal.processors.service.GridServiceProxy; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.services.Service; +import org.apache.ignite.services.ServiceDescriptor; + +/** + * Request to invoke service method. + */ +public class ClientServiceInvokeRequest extends ClientRequest { + /** Flag keep binary mask. */ + private static final byte FLAG_KEEP_BINARY_MASK = 0x01; + + /** Flag "has parameter types", indicates that method arguments prefixed by typeId to help to resolve methods. */ + private static final byte FLAG_PARAMETER_TYPES_MASK = 0x02; + + /** Methods cache. */ + private static final Map methodsCache = new ConcurrentHashMap<>(); + + /** Service name. */ + private final String name; + + /** Flags. */ + private final byte flags; + + /** Timeout. */ + private final long timeout; + + /** Nodes. */ + private final Collection nodeIds; + + /** Method name. */ + private final String methodName; + + /** Method parameter type IDs. */ + private final int[] paramTypeIds; + + /** Service arguments. */ + private final Object[] args; + + /** Objects reader. */ + private final BinaryRawReaderEx reader; + + /** + * Constructor. + * + * @param reader Reader. + */ + public ClientServiceInvokeRequest(BinaryReaderExImpl reader) { + super(reader); + + name = reader.readString(); + + flags = reader.readByte(); + + timeout = reader.readLong(); + + int cnt = reader.readInt(); + + nodeIds = new ArrayList<>(cnt); + + for (int i = 0; i < cnt; i++) + nodeIds.add(new UUID(reader.readLong(), reader.readLong())); + + methodName = reader.readString(); + + int argCnt = reader.readInt(); + + paramTypeIds = hasParameterTypes() ? new int[argCnt] : null; + + args = new Object[argCnt]; + + // We can't deserialize some types (arrays of user defined types for example) from detached objects. + // On the other hand, deserialize should be done as part of process() call (not in constructor) for proper + // error handling. + // To overcome these issues we store binary reader reference, parse request in constructor (by reading detached + // objects), restore arguments starting position in input stream and deserialize arguments from input stream + // in process() method. + this.reader = reader; + + int argsStartPos = reader.in().position(); + + for (int i = 0; i < argCnt; i++) { + if (paramTypeIds != null) + paramTypeIds[i] = reader.readInt(); + + args[i] = reader.readObjectDetached(); + } + + reader.in().position(argsStartPos); + } + + /** {@inheritDoc} */ + @Override public ClientResponse process(ClientConnectionContext ctx) { + if (F.isEmpty(name)) + throw new IgniteException("Service name can't be empty"); + + if (F.isEmpty(methodName)) + throw new IgniteException("Method name can't be empty"); + + ServiceDescriptor desc = findServiceDescriptor(ctx, name); + + Class svcCls = desc.serviceClass(); + + ClusterGroupAdapter grp = ctx.kernalContext().cluster().get(); + + if (ctx.securityContext() != null) + grp = (ClusterGroupAdapter)grp.forSubjectId(ctx.securityContext().subject().id()); + + grp = (ClusterGroupAdapter)(nodeIds.isEmpty() ? grp.forServers() : grp.forNodeIds(nodeIds)); + + IgniteServices services = grp.services(); + + if (!keepBinary() && args.length > 0) { + for (int i = 0; i < args.length; i++) { + if (paramTypeIds != null) + reader.readInt(); // Skip parameter typeId, we already read it in constructor. + + args[i] = reader.readObject(); + } + } + + try { + Object res; + + if (PlatformService.class.isAssignableFrom(svcCls)) { + PlatformService proxy = services.serviceProxy(name, PlatformService.class, false, timeout); + + res = proxy.invokeMethod(methodName, keepBinary(), !keepBinary(), args); + } + else { + GridServiceProxy proxy = new GridServiceProxy<>(grp, name, Service.class, false, timeout, + ctx.kernalContext()); + + Method method = resolveMethod(ctx, svcCls); + + res = proxy.invokeMethod(method, args); + } + + return new ClientObjectResponse(requestId(), res); + } + catch (PlatformNativeException e) { + ctx.kernalContext().log(getClass()).error("Failed to invoke platform service", e); + + throw new IgniteException("Failed to invoke platform service, see server logs for details"); + } + catch (Throwable e) { + throw new IgniteException(e); + } + } + + /** + * Keep binary flag. + */ + private boolean keepBinary() { + return (flags & FLAG_KEEP_BINARY_MASK) != 0; + } + + /** + * "Has parameter types" flag. + */ + private boolean hasParameterTypes() { + return (flags & FLAG_PARAMETER_TYPES_MASK) != 0; + } + + /** + * @param ctx Connection context. + * @param name Service name. + */ + private static ServiceDescriptor findServiceDescriptor(ClientConnectionContext ctx, String name) { + for (ServiceDescriptor desc : ctx.kernalContext().service().serviceDescriptors()) { + if (name.equals(desc.name())) + return desc; + } + + throw new IgniteException("Service not found: " + name); + } + + /** + * Resolve method by method name and parameter types or parameter values. + */ + private Method resolveMethod(ClientConnectionContext ctx, Class cls) throws ReflectiveOperationException { + if (paramTypeIds != null) { + MethodDescriptor desc = new MethodDescriptor(cls, methodName, paramTypeIds); + + Method method = methodsCache.get(desc); + + if (method != null) + return method; + + IgniteBinary binary = ctx.kernalContext().grid().binary(); + + for (Method method0 : cls.getMethods()) { + if (methodName.equals(method0.getName())) { + MethodDescriptor desc0 = MethodDescriptor.forMethod(binary, method0); + + methodsCache.putIfAbsent(desc0, method0); + + if (desc0.equals(desc)) + return method0; + } + } + + throw new NoSuchMethodException("Method not found: " + desc); + } + + // Try to find method by name and parameter values. + return PlatformServices.getMethod(cls, methodName, args); + } + + /** + * + */ + private static class MethodDescriptor { + /** Class. */ + private final Class cls; + + /** Method name. */ + private final String methodName; + + /** Parameter type IDs. */ + private final int[] paramTypeIds; + + /** Hash code. */ + private final int hash; + + /** + * @param cls Class. + * @param methodName Method name. + * @param paramTypeIds Parameter type ids. + */ + private MethodDescriptor(Class cls, String methodName, int[] paramTypeIds) { + assert cls != null; + assert methodName != null; + assert paramTypeIds != null; + + this.cls = cls; + this.methodName = methodName; + this.paramTypeIds = paramTypeIds; + + // Precalculate hash in constructor, since we need it for all objects of this class. + hash = 31 * ((31 * cls.hashCode()) + methodName.hashCode()) + Arrays.hashCode(paramTypeIds); + } + + /** + * @param binary Ignite binary. + * @param method Method. + */ + private static MethodDescriptor forMethod(IgniteBinary binary, Method method) { + Class[] paramTypes = method.getParameterTypes(); + + int[] paramTypeIds = new int[paramTypes.length]; + + for (int i = 0; i < paramTypes.length; i++) + paramTypeIds[i] = binary.typeId(paramTypes[i].getName()); + + return new MethodDescriptor(method.getDeclaringClass(), method.getName(), paramTypeIds); + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + MethodDescriptor that = (MethodDescriptor)o; + + return cls.equals(that.cls) && methodName.equals(that.methodName) + && Arrays.equals(paramTypeIds, that.paramTypeIds); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return hash; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(MethodDescriptor.class, this, "paramTypeIds", paramTypeIds); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java index 24d473accbe43..6a6e05ea7acb1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java @@ -514,6 +514,19 @@ private IgniteFuture dotnetDeployAllAsync(BinaryRawReaderEx reader, Ignite return cfgs; } + /** + * Finds a suitable method in a class. + * + * @param clazz Class. + * @param mthdName Name. + * @param args Args. + * @return Method. + * @throws NoSuchMethodException On error. + */ + public static Method getMethod(Class clazz, String mthdName, Object[] args) throws NoSuchMethodException { + return ServiceProxyHolder.getMethod(clazz, mthdName, args); + } + /** * Proxy holder. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java index 64eada3282feb..3c92ebcc92d3e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java @@ -191,8 +191,14 @@ else if (U.isToStringMethod(mtd)) if (svcCtx != null) { Service svc = svcCtx.service(); - if (svc != null) - return callServiceLocally(svc, mtd, args); + if (svc != null) { + try { + return callServiceLocally(svc, mtd, args); + } + catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } } } else { diff --git a/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java b/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java index 543321c53a458..9ac12e1eeebba 100644 --- a/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java +++ b/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java @@ -24,25 +24,22 @@ import javax.cache.Cache; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; -import org.apache.ignite.Ignition; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CachePeekMode; import org.apache.ignite.cache.query.Query; import org.apache.ignite.cache.query.QueryCursor; import org.apache.ignite.cache.query.ScanQuery; import org.apache.ignite.configuration.CacheConfiguration; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.client.thin.AbstractThinClientTest; import org.apache.ignite.testframework.GridTestUtils; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; /** * Async channel tests. */ -public class AsyncChannelTest extends GridCommonAbstractTest { +public class AsyncChannelTest extends AbstractThinClientTest { /** Nodes count. */ private static final int NODES_CNT = 3; @@ -52,9 +49,6 @@ public class AsyncChannelTest extends GridCommonAbstractTest { /** Cache name. */ private static final String CACHE_NAME = "tx_cache"; - /** Client connector address. */ - private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT; - /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName).setCacheConfiguration( @@ -75,7 +69,7 @@ public class AsyncChannelTest extends GridCommonAbstractTest { */ @Test public void testAsyncRequests() throws Exception { - try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) { + try (IgniteClient client = startClient(0)) { Ignite ignite = grid(0); IgniteCache igniteCache = ignite.cache(CACHE_NAME); @@ -130,7 +124,7 @@ public void testAsyncRequests() throws Exception { */ @Test public void testConcurrentRequests() throws Exception { - try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) { + try (IgniteClient client = startClient(0)) { ClientCache clientCache = client.cache(CACHE_NAME); clientCache.clear(); @@ -164,7 +158,7 @@ public void testConcurrentRequests() throws Exception { */ @Test public void testConcurrentQueries() throws Exception { - try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) { + try (IgniteClient client = startClient(0)) { ClientCache clientCache = client.cache(CACHE_NAME); clientCache.clear(); diff --git a/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java b/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java index f79928d86b5b7..bbb2c87ceafea 100644 --- a/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java @@ -20,25 +20,19 @@ import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; import org.apache.ignite.Ignite; -import org.apache.ignite.Ignition; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.client.thin.AbstractThinClientTest; import org.apache.ignite.spi.IgniteSpiException; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.testframework.GridTestUtils; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.Nullable; import org.junit.Test; /** * Checks that connection with starting node will be established correctly. */ -public class ConnectToStartingNodeTest extends GridCommonAbstractTest { - /** Client connector address. */ - private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT; - +public class ConnectToStartingNodeTest extends AbstractThinClientTest { /** Barrier to suspend discovery SPI start. */ private final CyclicBarrier barrier = new CyclicBarrier(2); @@ -75,7 +69,7 @@ public void testClientConnectBeforeDiscoveryStart() throws Exception { barrier.await(); IgniteInternalFuture futStartClient = GridTestUtils.runAsync( - () -> Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))); + () -> startClient(grid())); // Server doesn't accept connection before discovery SPI started. assertFalse(GridTestUtils.waitForCondition(futStartClient::isDone, 500L)); diff --git a/modules/core/src/test/java/org/apache/ignite/client/Person.java b/modules/core/src/test/java/org/apache/ignite/client/Person.java index c92305df39e34..27510a7bdde7b 100644 --- a/modules/core/src/test/java/org/apache/ignite/client/Person.java +++ b/modules/core/src/test/java/org/apache/ignite/client/Person.java @@ -19,6 +19,7 @@ import java.util.Objects; import org.apache.ignite.cache.query.annotations.QuerySqlField; +import org.apache.ignite.internal.util.typedef.internal.S; /** * A person entity used for the tests. @@ -62,4 +63,9 @@ public String getName() { return other.id.equals(id) && other.name.equals(name); } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(Person.class, this); + } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java new file mode 100644 index 0000000000000..b985a77d5055a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java @@ -0,0 +1,79 @@ +/* + * 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.client.thin; + +import java.util.Arrays; +import org.apache.ignite.Ignite; +import org.apache.ignite.Ignition; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +/** + * Abstract thin client test. + */ +public abstract class AbstractThinClientTest extends GridCommonAbstractTest { + /** + * Gets default client configuration. + */ + protected ClientConfiguration getClientConfiguration() { + return new ClientConfiguration(); + } + + /** + * Start thin client with configured endpoints to specified nodes. + * + * @param nodes Nodes to connect. + * @return Thin client. + */ + protected IgniteClient startClient(ClusterNode... nodes) { + String[] addrs = new String[nodes.length]; + + for (int i = 0; i < nodes.length; i++) { + ClusterNode node = nodes[i]; + + addrs[i] = F.first(node.addresses()) + ":" + node.attribute(ClientListenerProcessor.CLIENT_LISTENER_PORT); + } + + return Ignition.startClient(getClientConfiguration().setAddresses(addrs)); + } + + /** + * Start thin client with configured endpoints to specified ignite instances. + * + * @param ignites Ignite instances to connect. + * @return Thin client. + */ + protected IgniteClient startClient(Ignite... ignites) { + return startClient(Arrays.stream(ignites).map(ignite -> ignite.cluster().localNode()).toArray(ClusterNode[]::new)); + } + + /** + * Start thin client with configured endpoints to specified ignite instance indexes. + * + * @param igniteIdxs Ignite instance indexes to connect. + * @return Thin client. + */ + protected IgniteClient startClient(int... igniteIdxs) { + return startClient(Arrays.stream(igniteIdxs).mapToObj(igniteIdx -> grid(igniteIdx).cluster().localNode()) + .toArray(ClusterNode[]::new)); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java index 9f78342a0ce5b..5540ea3833a60 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java @@ -18,25 +18,18 @@ package org.apache.ignite.internal.client.thin; import org.apache.ignite.IgniteCluster; -import org.apache.ignite.Ignition; import org.apache.ignite.client.ClientCluster; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.cluster.ClusterState; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.configuration.DataRegionConfiguration; import org.apache.ignite.configuration.DataStorageConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; /** * Checks cluster state/WAL state operations for thin client. */ -public class ClusterApiTest extends GridCommonAbstractTest { - /** Client connector address. */ - private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT; - +public class ClusterApiTest extends AbstractThinClientTest { /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName) @@ -69,7 +62,7 @@ public class ClusterApiTest extends GridCommonAbstractTest { */ @Test public void testClusterState() throws Exception { - try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) { + try (IgniteClient client = startClient(0)) { ClientCluster clientCluster = client.cluster(); IgniteCluster igniteCluster = grid(0).cluster(); @@ -85,7 +78,7 @@ public void testClusterState() throws Exception { */ @Test public void testWalState() throws Exception { - try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) { + try (IgniteClient client = startClient(0)) { ClientCluster clientCluster = client.cluster(); IgniteCluster igniteCluster = grid(0).cluster(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java index e253fab7e4dc8..e3662042dda5d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java @@ -23,27 +23,20 @@ import java.util.Map; import java.util.UUID; import org.apache.ignite.Ignite; -import org.apache.ignite.Ignition; import org.apache.ignite.client.ClientClusterGroup; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; /** * Checks cluster groups for thin client. */ -public class ClusterGroupTest extends GridCommonAbstractTest { - /** Client connector address. */ - private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT; - +public class ClusterGroupTest extends AbstractThinClientTest { /** Grid index attribute name. */ private static final String GRID_IDX_ATTR_NAME = "GRID_IDX"; @@ -66,7 +59,7 @@ public class ClusterGroupTest extends GridCommonAbstractTest { startGrid(3, true, null); startGrid(4, true, F.asMap(CUSTOM_ATTR_NAME, CUSTOM_ATTR_VAL)); - client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR)); + client = startClient(0); } /** {@inheritDoc} */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java index d400daff1ae1a..1faab885e9905 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java @@ -33,10 +33,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; - import org.apache.ignite.Ignite; import org.apache.ignite.IgniteException; -import org.apache.ignite.Ignition; import org.apache.ignite.client.ClientCache; import org.apache.ignite.client.ClientClusterGroup; import org.apache.ignite.client.ClientCompute; @@ -44,7 +42,6 @@ import org.apache.ignite.client.IgniteClient; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.compute.ComputeTaskName; -import org.apache.ignite.configuration.ClientConfiguration; import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.configuration.ThinClientConfiguration; @@ -54,7 +51,6 @@ import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.mxbean.ClientProcessorMXBean; import org.apache.ignite.testframework.GridTestUtils; -import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.Nullable; import org.junit.Ignore; import org.junit.Test; @@ -62,7 +58,7 @@ /** * Checks compute grid funtionality of thin client. */ -public class ComputeTaskTest extends GridCommonAbstractTest { +public class ComputeTaskTest extends AbstractThinClientTest { /** Grids count. */ private static final int GRIDS_CNT = 4; @@ -84,18 +80,6 @@ public class ComputeTaskTest extends GridCommonAbstractTest { .setClientMode(getTestIgniteInstanceIndex(igniteInstanceName) == 3); } - /** - * - */ - private IgniteClient startClient(int... gridIdxs) { - String[] addrs = new String[gridIdxs.length]; - - for (int i = 0; i < gridIdxs.length; i++) - addrs[i] = "127.0.0.1:" + (ClientConnectorConfiguration.DFLT_PORT + gridIdxs[i]); - - return Ignition.startClient(new ClientConfiguration().setAddresses(addrs)); - } - /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { super.beforeTestsStarted(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java new file mode 100644 index 0000000000000..9f0641ab6bff7 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java @@ -0,0 +1,417 @@ +/* + * 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.client.thin; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.Ignite; +import org.apache.ignite.client.ClientClusterGroup; +import org.apache.ignite.client.ClientException; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.client.Person; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.resources.IgniteInstanceResource; +import org.apache.ignite.services.Service; +import org.apache.ignite.services.ServiceContext; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; + +/** + * Checks service invocation for thin client. + */ +public class ServicesTest extends AbstractThinClientTest { + /** Node-id service name. */ + private static final String NODE_ID_SERVICE_NAME = "node_id_svc"; + + /** Node-singleton service name. */ + private static final String NODE_SINGLTON_SERVICE_NAME = "node_svc"; + + /** Cluster-singleton service name. */ + private static final String CLUSTER_SINGLTON_SERVICE_NAME = "cluster_svc"; + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + startGrids(3); + + startClientGrid(3); + + grid(0).createCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + + grid(0).services().deployNodeSingleton(NODE_ID_SERVICE_NAME, new TestNodeIdService()); + + grid(0).services().deployNodeSingleton(NODE_SINGLTON_SERVICE_NAME, new TestService()); + + // Deploy CLUSTER_SINGLTON_SERVICE_NAME to grid(1). + int keyGrid1 = primaryKey(grid(1).cache(DEFAULT_CACHE_NAME)); + + grid(0).services().deployKeyAffinitySingleton(CLUSTER_SINGLTON_SERVICE_NAME, new TestService(), + DEFAULT_CACHE_NAME, keyGrid1); + } + + /** + * Test that overloaded methods resolved correctly. + */ + @Test + public void testOverloadedMethods() throws Exception { + try (IgniteClient client = startClient(0)) { + // Test local service calls (service deployed to each node). + TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME, + TestServiceInterface.class); + + checkOverloadedMethods(svc); + + // Test remote service calls (client connected to grid(0) but service deployed to grid(1)). + svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class); + + checkOverloadedMethods(svc); + } + } + + /** + * @param svc Service. + */ + private void checkOverloadedMethods(TestServiceInterface svc) { + assertEquals("testMethod()", svc.testMethod()); + + assertEquals("testMethod(String val): test", svc.testMethod("test")); + + assertEquals(123, svc.testMethod(123)); + + assertEquals("testMethod(Object val): test", svc.testMethod(new StringBuilder("test"))); + + assertEquals("testMethod(String val): null", svc.testMethod((String)null)); + + assertEquals("testMethod(Object val): null", svc.testMethod((Object)null)); + + Person person1 = new Person(1, "Person 1"); + Person person2 = new Person(2, "Person 2"); + + assertEquals("testMethod(Person person, Object obj): " + person1 + ", " + person2, + svc.testMethod(person1, (Object)person2)); + + assertEquals("testMethod(Object obj, Person person): " + person1 + ", " + person2, + svc.testMethod((Object)person1, person2)); + } + + /** + * Test that methods which get and return collections work correctly. + */ + @Test + public void testCollectionMethods() throws Exception { + try (IgniteClient client = startClient(0)) { + // Test local service calls (service deployed to each node). + TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME, + TestServiceInterface.class); + + checkCollectionMethods(svc); + + // Test remote service calls (client connected to grid(0) but service deployed to grid(1)). + svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class); + + checkCollectionMethods(svc); + } + } + + /** + * @param svc Service. + */ + private void checkCollectionMethods(TestServiceInterface svc) { + Person person1 = new Person(1, "Person 1"); + Person person2 = new Person(2, "Person 2"); + + Person[] arr = new Person[] {person1, person2}; + assertTrue(Arrays.equals(arr, svc.testArray(arr))); + + Collection col = new HashSet<>(F.asList(person1, person2)); + assertEquals(col, svc.testCollection(col)); + + Map map = F.asMap(1, person1, 2, person2); + assertEquals(map, svc.testMap(map)); + } + + /** + * Test that exception is thrown when invoking service with wrong interface. + */ + @Test + public void testWrongMethodInvocation() throws Exception { + try (IgniteClient client = startClient(0)) { + TestServiceInterface svc = client.services().serviceProxy(NODE_ID_SERVICE_NAME, + TestServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, () -> svc.testMethod(0), ClientException.class, + "Method not found"); + } + } + + /** + * Test that exception is thrown when trying to invoke non-existing service. + */ + @Test + public void testWrongServiceName() throws Exception { + try (IgniteClient client = startClient(0)) { + TestServiceInterface svc = client.services().serviceProxy("no_such_service", + TestServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, () -> svc.testMethod(0), ClientException.class, + "Service not found"); + } + } + + /** + * Test that service exception message is propagated to client. + */ + @Test + public void testServiceException() throws Exception { + try (IgniteClient client = startClient(0)) { + // Test local service calls (service deployed to each node). + TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME, + TestServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, svc::testException, ClientException.class, + "testException()"); + + // Test remote service calls (client connected to grid(0) but service deployed to grid(1)). + client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, svc::testException, ClientException.class, + "testException()"); + } + } + + /** + * Test that services executed on cluster group. + */ + @Test + public void testServicesOnClusterGroup() throws Exception { + try (IgniteClient client = startClient(0)) { + // Local node. + ClientClusterGroup grp = client.cluster().forNodeId(nodeId(0), nodeId(3)); + + TestNodeIdServiceInterface nodeSvc0 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME, + TestNodeIdServiceInterface.class); + + assertEquals(nodeId(0), nodeSvc0.nodeId()); + + // Remote node. + grp = client.cluster().forNodeId(nodeId(1), nodeId(3)); + + nodeSvc0 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME, + TestNodeIdServiceInterface.class); + + assertEquals(nodeId(1), nodeSvc0.nodeId()); + + // Client node. + grp = client.cluster().forNodeId(nodeId(3)); + + TestNodeIdServiceInterface nodeSvc1 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME, + TestNodeIdServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, nodeSvc1::nodeId, ClientException.class, + "Failed to find deployed service"); + + // All nodes, except service node. + grp = client.cluster().forNodeId(nodeId(0), nodeId(2), nodeId(3)); + + TestServiceInterface nodeSvc2 = client.services(grp).serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, + TestServiceInterface.class); + + GridTestUtils.assertThrowsAnyCause(log, nodeSvc2::testMethod, ClientException.class, + "Failed to find deployed service"); + } + } + + /** + * Test services timeout. + */ + @Test + public void testServiceTimeout() throws Exception { + long timeout = 100L; + + try (IgniteClient client = startClient(0)) { + TestServiceInterface svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, + TestServiceInterface.class, timeout); + + IgniteInternalFuture fut = GridTestUtils.runAsync(() -> svc.sleep(timeout * 2)); + + GridTestUtils.assertThrowsAnyCause(log, fut::get, ClientException.class, "timed out"); + } + } + + /** */ + public static interface TestServiceInterface { + /** */ + public String testMethod(); + + /** */ + public String testMethod(String val); + + /** */ + public String testMethod(Object val); + + /** */ + public int testMethod(int val); + + /** */ + public String testMethod(Person person, Object obj); + + /** */ + public String testMethod(Object obj, Person person); + + /** */ + public Person[] testArray(Person[] persons); + + /** */ + public Collection testCollection(Collection persons); + + /** */ + public Map testMap(Map persons); + + /** */ + public Object testException(); + + /** */ + public void sleep(long millis); + } + + /** + * Implementation of TestServiceInterface. + */ + public static class TestService implements Service, TestServiceInterface { + /** {@inheritDoc} */ + @Override public void cancel(ServiceContext ctx) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void init(ServiceContext ctx) throws Exception { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void execute(ServiceContext ctx) throws Exception { + // No-op. + } + + /** {@inheritDoc} */ + @Override public String testMethod() { + return "testMethod()"; + } + + /** {@inheritDoc} */ + @Override public String testMethod(String val) { + return "testMethod(String val): " + val; + } + + /** {@inheritDoc} */ + @Override public String testMethod(Object val) { + return "testMethod(Object val): " + val; + } + + /** {@inheritDoc} */ + @Override public int testMethod(int val) { + return val; + } + + /** {@inheritDoc} */ + @Override public String testMethod(Person person, Object obj) { + return "testMethod(Person person, Object obj): " + person + ", " + obj; + } + + /** {@inheritDoc} */ + @Override public String testMethod(Object obj, Person person) { + return "testMethod(Object obj, Person person): " + obj + ", " + person; + } + + /** {@inheritDoc} */ + @Override public Person[] testArray(Person[] persons) { + return persons; + } + + /** {@inheritDoc} */ + @Override public Collection testCollection(Collection persons) { + return persons; + } + + /** {@inheritDoc} */ + @Override public Map testMap(Map persons) { + return persons; + } + + /** {@inheritDoc} */ + @Override public Object testException() { + throw new IllegalStateException("testException()"); + } + + /** {@inheritDoc} */ + @Override public void sleep(long millis) { + long ts = U.currentTimeMillis(); + + doSleep(millis); + + // Make sure cached timestamp updated. + while (ts + millis >= U.currentTimeMillis()) + doSleep(100L); + } + } + + /** + * Service to return ID of node where method was executed. + */ + public static interface TestNodeIdServiceInterface { + /** Gets ID of node where method was executed */ + public UUID nodeId(); + } + + /** + * Implementation of TestNodeIdServiceInterface + */ + public static class TestNodeIdService implements Service, TestNodeIdServiceInterface { + /** Ignite. */ + @IgniteInstanceResource + Ignite ignite; + + /** {@inheritDoc} */ + @Override public void cancel(ServiceContext ctx) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void init(ServiceContext ctx) throws Exception { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void execute(ServiceContext ctx) throws Exception { + // No-op. + } + + /** {@inheritDoc} */ + @Override public UUID nodeId() { + return ignite.cluster().localNode().id(); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java index 8609314e66138..bb5239bae535c 100644 --- a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java +++ b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java @@ -27,15 +27,19 @@ import java.util.UUID; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteException; +import org.apache.ignite.Ignition; import org.apache.ignite.binary.BinaryObjectException; import org.apache.ignite.binary.BinaryReader; import org.apache.ignite.binary.BinaryWriter; import org.apache.ignite.binary.Binarylizable; +import org.apache.ignite.client.IgniteClient; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJob; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.compute.ComputeTaskAdapter; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.services.ServiceDescriptor; @@ -96,6 +100,11 @@ public abstract class AbstractPlatformServiceCallTask extends ComputeTaskAdapter /** */ protected abstract static class AbstractServiceCallJob extends ComputeJobAdapter { + /** */ + @SuppressWarnings("unused") + @IgniteInstanceResource + protected transient Ignite ignite; + /** */ protected final String srvcName; @@ -120,10 +129,31 @@ protected AbstractServiceCallJob(String srvcName) { } } + /** + * Gets service proxy. + */ + TestPlatformService serviceProxy() { + return ignite.services().serviceProxy(srvcName, TestPlatformService.class, false); + } + /** * Test method to call platform service. */ abstract void runTest(); + + /** + * Start thin client connected to current ignite instance. + */ + IgniteClient startClient() { + ClusterNode node = ignite.cluster().localNode(); + + String addr = F.first(node.addresses()) + ":" + node.attribute(ClientListenerProcessor.CLIENT_LISTENER_PORT); + + return Ignition.startClient(new ClientConfiguration() + .setAddresses(addr) + .setBinaryConfiguration(ignite.configuration().getBinaryConfiguration()) + ); + } } /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java index 7e2806b08a59e..65f48d91cd468 100644 --- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java @@ -22,10 +22,8 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.apache.ignite.Ignite; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.internal.util.typedef.T2; -import org.apache.ignite.resources.IgniteInstanceResource; /** * Test invoke methods with collections and arrays as arguments and return type. @@ -38,11 +36,6 @@ public class PlatformServiceCallCollectionsTask extends AbstractPlatformServiceC /** */ public static class PlatformServiceCallCollectionsJob extends AbstractServiceCallJob { - /** */ - @SuppressWarnings("unused") - @IgniteInstanceResource - private transient Ignite ignite; - /** * @param svcName Service name. */ @@ -52,7 +45,7 @@ public static class PlatformServiceCallCollectionsJob extends AbstractServiceCal /** {@inheritDoc} */ @Override void runTest() { - TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false); + TestPlatformService srv = serviceProxy(); { TestValue[] exp = IntStream.range(0, 10).mapToObj(i -> new TestValue(i, "name_" + i)) diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java new file mode 100644 index 0000000000000..d874edf16d9b9 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java @@ -0,0 +1,65 @@ +/* + * 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.platform; + +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.compute.ComputeJobAdapter; +import org.apache.ignite.internal.processors.platform.services.PlatformService; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** + * Test invoke {@link PlatformService} methods with collections and arrays as arguments and return type from + * java thin client. + */ +public class PlatformServiceCallCollectionsThinTask extends AbstractPlatformServiceCallTask { + /** {@inheritDoc} */ + @Override ComputeJobAdapter createJob(String svcName) { + return new PlatformServiceCallCollectionsThinJob(svcName); + } + + /** */ + static class PlatformServiceCallCollectionsThinJob extends + PlatformServiceCallCollectionsTask.PlatformServiceCallCollectionsJob { + /** Thin client. */ + IgniteClient client; + + /** + * @param srvcName Service name. + */ + PlatformServiceCallCollectionsThinJob(String srvcName) { + super(srvcName); + } + + /** {@inheritDoc} */ + @Override TestPlatformService serviceProxy() { + return client.services().serviceProxy(srvcName, TestPlatformService.class); + } + + /** {@inheritDoc} */ + @Override void runTest() { + client = startClient(); + + try { + super.runTest(); + } + finally { + U.close(client, ignite.log().getLogger(getClass())); + } + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java index f39a3ee98790f..97c0d54c9d6ae 100644 --- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java @@ -18,11 +18,9 @@ package org.apache.ignite.platform; import java.util.UUID; -import org.apache.ignite.Ignite; import org.apache.ignite.compute.ComputeJobAdapter; import org.apache.ignite.internal.processors.platform.PlatformNativeException; import org.apache.ignite.internal.processors.platform.services.PlatformService; -import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.testframework.GridTestUtils; /** @@ -36,11 +34,6 @@ public class PlatformServiceCallTask extends AbstractPlatformServiceCallTask { /** */ static class PlatformServiceCallJob extends AbstractServiceCallJob { - /** */ - @SuppressWarnings("unused") - @IgniteInstanceResource - private transient Ignite ignite; - /** * @param srvcName Service name. */ @@ -50,32 +43,41 @@ static class PlatformServiceCallJob extends AbstractServiceCallJob { /** {@inheritDoc} */ @Override void runTest() { - TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false); + TestPlatformService srv = serviceProxy(); - { - UUID nodeId = srv.getNodeId(); - assertTrue(ignite.cluster().nodes().stream().anyMatch(n -> n.id().equals(nodeId))); - } + checkNodeId(srv); + checkUuidProp(srv); + checkObjectProp(srv); + checkErrorMethod(srv); + } - { - UUID expUuid = UUID.randomUUID(); - srv.setGuidProp(expUuid); - assertEquals(expUuid, srv.getGuidProp()); - } + /** */ + protected void checkNodeId(TestPlatformService srv) { + UUID nodeId = srv.getNodeId(); + assertTrue(ignite.cluster().nodes().stream().anyMatch(n -> n.id().equals(nodeId))); + } - { - TestValue exp = new TestValue(1, "test"); - srv.setValueProp(exp); - assertEquals(exp, srv.getValueProp()); - } + /** */ + protected void checkUuidProp(TestPlatformService srv) { + UUID expUuid = UUID.randomUUID(); + srv.setGuidProp(expUuid); + assertEquals(expUuid, srv.getGuidProp()); + } - { - PlatformNativeException nativeEx = (PlatformNativeException)GridTestUtils - .assertThrowsWithCause(srv::errorMethod, PlatformNativeException.class) - .getCause(); + /** */ + protected void checkObjectProp(TestPlatformService srv) { + TestValue exp = new TestValue(1, "test"); + srv.setValueProp(exp); + assertEquals(exp, srv.getValueProp()); + } + + /** */ + protected void checkErrorMethod(TestPlatformService srv) { + PlatformNativeException nativeEx = (PlatformNativeException)GridTestUtils + .assertThrowsWithCause(srv::errorMethod, PlatformNativeException.class) + .getCause(); - assertTrue(nativeEx.toString().contains("Failed method")); - } + assertTrue(nativeEx.toString().contains("Failed method")); } } } diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java new file mode 100644 index 0000000000000..a1c840f3fb7ac --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java @@ -0,0 +1,76 @@ +/* + * 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.platform; + +import org.apache.ignite.client.ClientException; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.compute.ComputeJobAdapter; +import org.apache.ignite.internal.processors.platform.services.PlatformService; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.testframework.GridTestUtils; + +/** + * Basic task to calling {@link PlatformService} from java thin client. + */ +public class PlatformServiceCallThinTask extends AbstractPlatformServiceCallTask { + /** {@inheritDoc} */ + @Override ComputeJobAdapter createJob(String svcName) { + return new PlatformServiceCallThinJob(svcName); + } + + /** */ + static class PlatformServiceCallThinJob extends PlatformServiceCallTask.PlatformServiceCallJob { + /** Thin client. */ + IgniteClient client; + + /** + * @param srvcName Service name. + */ + PlatformServiceCallThinJob(String srvcName) { + super(srvcName); + } + + /** {@inheritDoc} */ + @Override TestPlatformService serviceProxy() { + return client.services().serviceProxy(srvcName, TestPlatformService.class); + } + + /** {@inheritDoc} */ + @Override void runTest() { + client = startClient(); + + try { + super.runTest(); + } + finally { + U.close(client, ignite.log().getLogger(getClass())); + } + } + + /** */ + @Override protected void checkErrorMethod(TestPlatformService srv) { + // For thin client only top level error message is passed from server to client, so we should override + // method for error check. + GridTestUtils.assertThrowsAnyCause(null, () -> { + srv.errorMethod(); + return null; + }, ClientException.class, "Failed to invoke platform service"); + } + + } +} diff --git a/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java index 07ac59fd5fdb5..c093efbf9b42a 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java +++ b/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java @@ -20,6 +20,7 @@ import org.apache.ignite.internal.client.thin.ClusterApiTest; import org.apache.ignite.internal.client.thin.ClusterGroupTest; import org.apache.ignite.internal.client.thin.ComputeTaskTest; +import org.apache.ignite.internal.client.thin.ServicesTest; import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessResourceReleaseTest; import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessStableTopologyTest; import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessUnstableTopologyTest; @@ -47,6 +48,7 @@ ComputeTaskTest.class, ClusterApiTest.class, ClusterGroupTest.class, + ServicesTest.class, ThinClientPartitionAwarenessStableTopologyTest.class, ThinClientPartitionAwarenessUnstableTopologyTest.class, ThinClientPartitionAwarenessResourceReleaseTest.class diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs index b06671a1ee448..3efe0e82cd5f4 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs @@ -39,6 +39,13 @@ public class CallPlatformServiceTest /** */ private const string CheckCollectionsTaskName = "org.apache.ignite.platform.PlatformServiceCallCollectionsTask"; + /** */ + private const string CheckThinTaskName = "org.apache.ignite.platform.PlatformServiceCallThinTask"; + + /** */ + private const string CheckCollectionsThinTaskName = + "org.apache.ignite.platform.PlatformServiceCallCollectionsThinTask"; + /** */ protected IIgnite Grid1; @@ -70,11 +77,13 @@ public void TearDown() /// Tests call a platform service by invoking a special compute java task, /// in which real invocation of the service is made. /// - /// Tests common methods. /// If true call on local node. + /// Task to test. /// [Test] - public void TestCallPlatformService([Values(true, false)] bool local) + public void TestCallPlatformService([Values(true, false)] bool local, + [Values(CheckTaskName, CheckCollectionsTaskName, CheckThinTaskName, CheckCollectionsThinTaskName)] + string taskName) { var cfg = new ServiceConfiguration { @@ -85,31 +94,9 @@ public void TestCallPlatformService([Values(true, false)] bool local) Grid1.GetServices().Deploy(cfg); - Grid1.GetCompute().ExecuteJavaTask(CheckTaskName, new object[] { ServiceName, local }); + Grid1.GetCompute().ExecuteJavaTask(taskName, new object[] { ServiceName, local }); } - - /// - /// Tests call a platform service by invoking a special compute java task, - /// in which real invocation of the service is made. - /// - /// Tests collections method. - /// If true call on local node. - /// - [Test] - public void TestCallPlatformServiceCollections([Values(true, false)] bool local) - { - var cfg = new ServiceConfiguration - { - Name = ServiceName, - TotalCount = 1, - Service = new TestPlatformService() - }; - - Grid1.GetServices().Deploy(cfg); - Grid1.GetCompute().ExecuteJavaTask(CheckCollectionsTaskName, new object[] { ServiceName, local }); - } - /// /// Starts the grids. /// diff --git a/modules/yardstick/config/benchmark-thin-services.properties b/modules/yardstick/config/benchmark-thin-services.properties new file mode 100644 index 0000000000000..9de1c74a7e5db --- /dev/null +++ b/modules/yardstick/config/benchmark-thin-services.properties @@ -0,0 +1,78 @@ +# 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. + +# +# Contains benchmarks for distributed computations. +# + +# JVM options. +JVM_OPTS=${JVM_OPTS}" -DIGNITE_QUIET=false" + +# Uncomment to enable concurrent garbage collection (GC) if you encounter long GC pauses. +# JVM_OPTS=${JVM_OPTS}" \ +# -Xms6g \ +# -Xmx6g \ +# -Xloggc:./gc${now0}.log \ +# -XX:+PrintGCDetails \ +# -verbose:gc \ +# -XX:+UseParNewGC \ +# -XX:+UseConcMarkSweepGC \ +# " + +#Ignite version +ver="RELEASE-" + +# List of default probes. +# Add DStatProbe or VmStatProbe if your OS supports it (e.g. if running on Linux). +BENCHMARK_DEFAULT_PROBES=ThroughputLatencyProbe,PercentileProbe + +# Packages where the specified benchmark is searched by reflection mechanism. +BENCHMARK_PACKAGES=org.yardstickframework,org.apache.ignite.yardstick + +# Probe point writer class name. +# BENCHMARK_WRITER= + +# Comma-separated list of the hosts to run BenchmarkServers on. 2 nodes on local host are enabled by default. +SERVER_HOSTS=localhost,localhost + +# Comma-separated list of the hosts to run BenchmarkDrivers on. 1 node on local host is enabled by default. +DRIVER_HOSTS=localhost + +# Remote username. +# REMOTE_USER= + +# Number of nodes, used to wait for the specified number of nodes to start. +nodesNum=$((`echo ${SERVER_HOSTS} | tr ',' '\n' | wc -l` + `echo ${DRIVER_HOSTS} | tr ',' '\n' | wc -l`)) + +# Backups count. +b=1 + +# Warmup. +w=60 + +# Duration. +d=180 + +# Threads count. +t=64 + +# Sync mode. +sm=PRIMARY_SYNC + +# Run configuration. +# Note that each benchmark is set to run for 300 seconds (5 min) with warm-up set to 60 seconds (1 minute). +CONFIGS="\ +-cfg ${SCRIPT_DIR}/../config/ignite-services-config.xml -nn ${nodesNum} -b ${b} -w ${w} -d ${d} -t ${t} -sm ${sm} -dn IgniteThinServiceInvocationBenchmark -sn IgniteNode -ds ${ver}thin-service-invoke\ +" diff --git a/modules/yardstick/config/ignite-services-config.xml b/modules/yardstick/config/ignite-services-config.xml new file mode 100644 index 0000000000000..33a11832e99b2 --- /dev/null +++ b/modules/yardstick/config/ignite-services-config.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/IgniteThinServiceInvocationBenchmark.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/IgniteThinServiceInvocationBenchmark.java new file mode 100644 index 0000000000000..a17ab5dc577ab --- /dev/null +++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/IgniteThinServiceInvocationBenchmark.java @@ -0,0 +1,45 @@ +/* + * 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.yardstick.thin.service; + +import java.util.Map; +import org.apache.ignite.yardstick.IgniteThinAbstractBenchmark; +import org.yardstickframework.BenchmarkConfiguration; + +/** + * Class to benchmark thin client service invocation. + */ +public class IgniteThinServiceInvocationBenchmark extends IgniteThinAbstractBenchmark { + /** Service proxy. */ + private volatile ThreadLocal srvcProxy; + + /** {@inheritDoc} */ + @Override public void setUp(BenchmarkConfiguration cfg) throws Exception { + super.setUp(cfg); + + srvcProxy = ThreadLocal.withInitial( + () -> client().services().serviceProxy(SimpleService.class.getSimpleName(), SimpleService.class)); + } + + /** {@inheritDoc} */ + @Override public boolean test(Map map) throws Exception { + srvcProxy.get().echo(nextRandom(Integer.MAX_VALUE)); + + return true; + } +} diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleService.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleService.java new file mode 100644 index 0000000000000..5447a28730e3b --- /dev/null +++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleService.java @@ -0,0 +1,28 @@ +/* + * 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.yardstick.thin.service; + +/** + * Simple service interface. + */ +public interface SimpleService { + /** + * @param val Value. + */ + public int echo(int val); +} diff --git a/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleServiceImpl.java b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleServiceImpl.java new file mode 100644 index 0000000000000..1cf108fc23a69 --- /dev/null +++ b/modules/yardstick/src/main/java/org/apache/ignite/yardstick/thin/service/SimpleServiceImpl.java @@ -0,0 +1,46 @@ +/* + * 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.yardstick.thin.service; + +import org.apache.ignite.services.Service; +import org.apache.ignite.services.ServiceContext; + +/** + * Simple service implementation. + */ +public class SimpleServiceImpl implements SimpleService, Service { + /** {@inheritDoc} */ + @Override public void cancel(ServiceContext ctx) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void init(ServiceContext ctx) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void execute(ServiceContext ctx) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public int echo(int val) { + return val; + } +}