From 669069f02f3a7447d07c86d506af7a5860578035 Mon Sep 17 00:00:00 2001 From: Brian Nichols Date: Tue, 1 Aug 2023 14:08:21 -0700 Subject: [PATCH] CLIENT-2454 Support new proxy client that is used in our database-as-a-service (dbaas) offering. - Use IAerospikeClient interface in examples, benchmarks and test. - Add new proxy client directory. Main class is AerospikeClientProxy. - Add SuiteProxy for tests that can be run against the proxy server. --- .github/workflows/build-proxy.yml | 25 + .gitignore | 3 +- README.md | 21 +- benchmarks/pom.xml | 43 +- .../aerospike/benchmarks/InsertTaskAsync.java | 8 +- .../aerospike/benchmarks/InsertTaskSync.java | 8 +- .../src/com/aerospike/benchmarks/Main.java | 102 +- .../com/aerospike/benchmarks/RWTaskAsync.java | 8 +- .../com/aerospike/benchmarks/RWTaskSync.java | 8 +- client/pom.xml | 8 +- .../com/aerospike/client/AerospikeClient.java | 4 +- .../aerospike/client/AerospikeException.java | 7 + .../aerospike/client/async/AsyncCommand.java | 4 +- .../aerospike/client/async/AsyncOperate.java | 2 +- .../client/async/NettyEventLoops.java | 27 +- .../com/aerospike/client/command/Command.java | 195 +- .../aerospike/client/command/OperateArgs.java | 12 +- .../client/command/OperateCommand.java | 2 +- .../aerospike/client/command/SyncCommand.java | 9 - .../com/aerospike/client/query/Filter.java | 48 +- .../client/query/PartitionFilter.java | 19 + .../client/query/PartitionTracker.java | 83 +- .../com/aerospike/client/query/RecordSet.java | 24 +- .../com/aerospike/client/query/ResultSet.java | 30 +- .../aerospike/client/task/ExecuteTask.java | 24 +- examples/pom.xml | 39 +- examples/src/com/aerospike/examples/Add.java | 4 +- .../src/com/aerospike/examples/Append.java | 4 +- .../com/aerospike/examples/AsyncBatch.java | 6 +- .../com/aerospike/examples/AsyncExample.java | 33 +- .../com/aerospike/examples/AsyncPutGet.java | 16 +- .../com/aerospike/examples/AsyncQuery.java | 21 +- .../src/com/aerospike/examples/AsyncScan.java | 6 +- .../com/aerospike/examples/AsyncScanPage.java | 8 +- .../examples/AsyncUserDefinedFunction.java | 18 +- .../src/com/aerospike/examples/Batch.java | 14 +- .../com/aerospike/examples/BatchOperate.java | 18 +- .../src/com/aerospike/examples/DeleteBin.java | 4 +- .../src/com/aerospike/examples/Example.java | 11 +- .../src/com/aerospike/examples/Expire.java | 6 +- .../com/aerospike/examples/Generation.java | 4 +- .../src/com/aerospike/examples/ListMap.java | 14 +- examples/src/com/aerospike/examples/Main.java | 13 + .../src/com/aerospike/examples/Operate.java | 10 +- .../com/aerospike/examples/OperateBit.java | 6 +- .../com/aerospike/examples/OperateList.java | 8 +- .../com/aerospike/examples/OperateMap.java | 16 +- .../com/aerospike/examples/Parameters.java | 1 + .../src/com/aerospike/examples/Prepend.java | 4 +- .../src/com/aerospike/examples/PutGet.java | 8 +- .../com/aerospike/examples/QueryAverage.java | 25 +- .../aerospike/examples/QueryCollection.java | 10 +- .../com/aerospike/examples/QueryExecute.java | 14 +- .../src/com/aerospike/examples/QueryExp.java | 28 +- .../com/aerospike/examples/QueryFilter.java | 14 +- .../examples/QueryGeoCollection.java | 24 +- .../com/aerospike/examples/QueryInteger.java | 10 +- .../src/com/aerospike/examples/QueryPage.java | 10 +- .../com/aerospike/examples/QueryRegion.java | 12 +- .../aerospike/examples/QueryRegionFilter.java | 12 +- .../com/aerospike/examples/QueryResume.java | 14 +- .../com/aerospike/examples/QueryString.java | 10 +- .../src/com/aerospike/examples/QuerySum.java | 12 +- .../src/com/aerospike/examples/Replace.java | 10 +- .../src/com/aerospike/examples/ScanPage.java | 8 +- .../com/aerospike/examples/ScanParallel.java | 6 +- .../com/aerospike/examples/ScanResume.java | 8 +- .../com/aerospike/examples/ScanSeries.java | 6 +- .../com/aerospike/examples/ServerInfo.java | 6 +- .../src/com/aerospike/examples/StoreKey.java | 10 +- .../src/com/aerospike/examples/Touch.java | 25 +- .../examples/UserDefinedFunction.java | 31 +- pom.xml | 36 +- proxy/README.md | 18 + proxy/pom.xml | 107 + proxy/resources/project.properties | 2 + .../client/proxy/AerospikeClientFactory.java | 46 + .../client/proxy/AerospikeClientProxy.java | 2806 +++++++++++++++++ .../proxy/BackgroundExecuteCommandProxy.java | 87 + .../aerospike/client/proxy/BatchProxy.java | 956 ++++++ .../aerospike/client/proxy/CommandProxy.java | 202 ++ .../client/proxy/DeleteCommandProxy.java | 87 + .../client/proxy/ExecuteCommandProxy.java | 110 + .../client/proxy/ExecuteTaskProxy.java | 60 + .../proxy/ExecuteTaskStatusCommandProxy.java | 96 + .../client/proxy/ExistsCommandProxy.java | 85 + .../client/proxy/MultiCommandProxy.java | 138 + .../client/proxy/OperateCommandProxy.java | 55 + .../com/aerospike/client/proxy/Parser.java | 245 ++ .../proxy/QueryAggregateCommandProxy.java | 250 ++ .../client/proxy/QueryCommandProxy.java | 71 + .../client/proxy/ReadCommandProxy.java | 154 + .../client/proxy/ReadHeaderCommandProxy.java | 84 + .../aerospike/client/proxy/RecordProxy.java | 51 + .../RecordSequenceListenerToCallback.java | 50 + .../client/proxy/RecordSequenceRecordSet.java | 171 + .../proxy/RecordSequenceToQueryListener.java | 50 + .../client/proxy/ResultSetProxy.java | 207 ++ .../client/proxy/ScanCommandProxy.java | 80 + .../proxy/ScanQueryBaseCommandProxy.java | 114 + .../client/proxy/SingleCommandProxy.java | 53 + .../client/proxy/TouchCommandProxy.java | 80 + .../client/proxy/WriteCommandProxy.java | 88 + .../client/proxy/auth/AuthTokenManager.java | 409 +++ .../BearerTokenCallCredentials.java | 58 + .../grpc/DefaultGrpcChannelSelector.java | 68 + .../proxy/grpc/DefaultGrpcStreamSelector.java | 161 + .../client/proxy/grpc/GrpcCallExecutor.java | 292 ++ .../proxy/grpc/GrpcChannelExecutor.java | 690 ++++ .../proxy/grpc/GrpcChannelProvider.java | 37 + .../proxy/grpc/GrpcChannelSelector.java | 33 + .../client/proxy/grpc/GrpcClientPolicy.java | 383 +++ .../client/proxy/grpc/GrpcConversions.java | 423 +++ .../client/proxy/grpc/GrpcStream.java | 473 +++ .../client/proxy/grpc/GrpcStreamSelector.java | 83 + .../client/proxy/grpc/GrpcStreamingCall.java | 217 ++ .../grpc/MultiAddressNameResolverFactory.java | 59 + .../proxy/grpc/SingleEventLoopGroup.java | 188 ++ test/pom.xml | 19 +- test/src/com/aerospike/test/SuiteAsync.java | 27 +- test/src/com/aerospike/test/SuiteProxy.java | 101 + test/src/com/aerospike/test/SuiteSync.java | 12 +- .../com/aerospike/test/async/TestAsync.java | 6 +- .../aerospike/test/async/TestAsyncPutGet.java | 10 +- .../aerospike/test/async/TestAsyncUDF.java | 4 + .../src/com/aerospike/test/sync/TestSync.java | 6 +- .../aerospike/test/sync/basic/TestAdd.java | 10 +- .../test/sync/basic/TestFilterExp.java | 14 +- .../test/sync/basic/TestOperateBit.java | 47 +- .../test/sync/basic/TestOperateHll.java | 27 +- .../aerospike/test/sync/basic/TestScan.java | 6 +- .../test/sync/basic/TestServerInfo.java | 4 + .../aerospike/test/sync/basic/TestUDF.java | 4 + test/src/com/aerospike/test/util/Args.java | 57 +- 134 files changed, 11374 insertions(+), 514 deletions(-) create mode 100644 .github/workflows/build-proxy.yml create mode 100644 proxy/README.md create mode 100644 proxy/pom.xml create mode 100644 proxy/resources/project.properties create mode 100644 proxy/src/com/aerospike/client/proxy/AerospikeClientFactory.java create mode 100644 proxy/src/com/aerospike/client/proxy/AerospikeClientProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/BackgroundExecuteCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/BatchProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/CommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/DeleteCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ExecuteCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ExecuteTaskProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ExecuteTaskStatusCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ExistsCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/MultiCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/OperateCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/Parser.java create mode 100644 proxy/src/com/aerospike/client/proxy/QueryAggregateCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/QueryCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ReadCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ReadHeaderCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/RecordProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/RecordSequenceListenerToCallback.java create mode 100644 proxy/src/com/aerospike/client/proxy/RecordSequenceRecordSet.java create mode 100644 proxy/src/com/aerospike/client/proxy/RecordSequenceToQueryListener.java create mode 100644 proxy/src/com/aerospike/client/proxy/ResultSetProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ScanCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/ScanQueryBaseCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/SingleCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/TouchCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/WriteCommandProxy.java create mode 100644 proxy/src/com/aerospike/client/proxy/auth/AuthTokenManager.java create mode 100644 proxy/src/com/aerospike/client/proxy/auth/credentials/BearerTokenCallCredentials.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcChannelSelector.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcStreamSelector.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcCallExecutor.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelExecutor.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelProvider.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelSelector.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcClientPolicy.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcConversions.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcStream.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamSelector.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamingCall.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/MultiAddressNameResolverFactory.java create mode 100644 proxy/src/com/aerospike/client/proxy/grpc/SingleEventLoopGroup.java create mode 100644 test/src/com/aerospike/test/SuiteProxy.java diff --git a/.github/workflows/build-proxy.yml b/.github/workflows/build-proxy.yml new file mode 100644 index 000000000..47ee21021 --- /dev/null +++ b/.github/workflows/build-proxy.yml @@ -0,0 +1,25 @@ +name: Build and upload proxy client to JFrog + +on: + push: + branches: + - stage + # TODO: snapshots_private has been removed from base parent pom.xml. Need to add workflow code to write snapshots_private to local pipeline pom.xml (this workflow will not work until that is done) + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Java client + uses: actions/checkout@v2 + + - name: Set up settings.xml for Maven + uses: s4u/maven-settings-action@v2.8.0 + with: + servers: '[{"id": "snapshots_private", "username": "${{ secrets.JFROG_USERNAME }}", "password": "${{ secrets.JFROG_MAVEN_TOKEN }}"}]' + + - name: Build Java client + run: mvn install + + - name: Upload to JFrog + run: mvn deploy diff --git a/.gitignore b/.gitignore index b629b348f..56939ee10 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ apidocs *.out *.jar *.iml -.idea/ \ No newline at end of file +.idea/ +dependency-reduced-pom.xml diff --git a/README.md b/README.md index 2cb4bc965..e5927a7bf 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,16 @@ Aerospike Java Client Package Aerospike Java client. This package contains full source code for these projects. -* client: Java client library. -* examples: Java client examples. -* benchmarks: Java client benchmarks. -* test: Java client unit tests. +* client: Java native client library. +* proxy: Java proxy client library for dbaas (database as a service). +* examples: Java client examples. +* benchmarks: Java client benchmarks. +* test: Java client unit tests. Prerequisites: -* Java 1.8 or greater. -* Maven 3.0 or greater. - -AerospikeClient now supports synchronous and asynchronous methods. Asynchronous -methods can utilize either Netty event loops or direct NIO event loops. - -The Netty library artifacts (netty-transport and netty-handler) are declared optional. -If your application's build file (pom.xml) declares these Netty library artifacts as -dependencies, then the Netty libraries will be included in your application's jar. -Otherwise, you application's jar will not include any Netty code. +* Java 8+ +* Maven 3.0+ The source code can be imported into any Java IDE. Maven build scripts are also provided. diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 9ecaba5ac..0c3498529 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -18,6 +18,11 @@ aerospike-client + + com.aerospike + aerospike-proxy-client + + io.netty netty-transport @@ -61,34 +66,30 @@ maven-compiler-plugin - maven-assembly-plugin - - - jar-with-dependencies - - - - com.aerospike.benchmarks.Main - - - + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 - make-my-jar-with-dependencies - package - single + shade + + true + jar-with-dependencies + + + + + com.aerospike.benchmarks.Main + + + + - - - resources - true - - - + diff --git a/benchmarks/src/com/aerospike/benchmarks/InsertTaskAsync.java b/benchmarks/src/com/aerospike/benchmarks/InsertTaskAsync.java index 086ba8e5e..a2e0f45ad 100644 --- a/benchmarks/src/com/aerospike/benchmarks/InsertTaskAsync.java +++ b/benchmarks/src/com/aerospike/benchmarks/InsertTaskAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.benchmarks; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.async.EventLoop; import com.aerospike.client.listener.WriteListener; @@ -26,7 +26,7 @@ public final class InsertTaskAsync extends InsertTask { - private final AerospikeClient client; + private final IAerospikeClient client; private final EventLoop eventLoop; private final RandomShift random; private final WriteListener listener; @@ -37,7 +37,7 @@ public final class InsertTaskAsync extends InsertTask { private final boolean useLatency; public InsertTaskAsync( - AerospikeClient client, + IAerospikeClient client, EventLoop eventLoop, Arguments args, CounterStore counters, diff --git a/benchmarks/src/com/aerospike/benchmarks/InsertTaskSync.java b/benchmarks/src/com/aerospike/benchmarks/InsertTaskSync.java index 73b03c620..54e0fc52d 100644 --- a/benchmarks/src/com/aerospike/benchmarks/InsertTaskSync.java +++ b/benchmarks/src/com/aerospike/benchmarks/InsertTaskSync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,20 +16,20 @@ */ package com.aerospike.benchmarks; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.util.RandomShift; import com.aerospike.client.util.Util; public final class InsertTaskSync extends InsertTask implements Runnable { - private final AerospikeClient client; + private final IAerospikeClient client; private final long keyStart; private final long keyCount; - public InsertTaskSync(AerospikeClient client, Arguments args, CounterStore counters, long keyStart, long keyCount) { + public InsertTaskSync(IAerospikeClient client, Arguments args, CounterStore counters, long keyStart, long keyCount) { super(args, counters); this.client = client; this.keyStart = keyStart; diff --git a/benchmarks/src/com/aerospike/benchmarks/Main.java b/benchmarks/src/com/aerospike/benchmarks/Main.java index 405168623..ceb8bffd4 100644 --- a/benchmarks/src/com/aerospike/benchmarks/Main.java +++ b/benchmarks/src/com/aerospike/benchmarks/Main.java @@ -33,8 +33,8 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Log; import com.aerospike.client.Log.Level; @@ -57,9 +57,11 @@ import com.aerospike.client.policy.Replica; import com.aerospike.client.policy.TlsPolicy; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.AerospikeClientFactory; import com.aerospike.client.util.Util; import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -98,6 +100,7 @@ public static void main(String[] args) { private int nThreads; private int asyncMaxCommands = 100; private int eventLoopSize = 1; + private boolean useProxyClient; private boolean asyncEnabled; private boolean initialize; private boolean batchShowNodes; @@ -340,6 +343,8 @@ public Main(String[] commandLineArgs) throws Exception { "Value: DIRECT_NIO | NETTY_NIO | NETTY_EPOLL | NETTY_KQUEUE | NETTY_IOURING" ); + options.addOption("proxy", false, "Use proxy client."); + options.addOption("upn", "udfPackageName", true, "Specify the package name where the udf function is located"); options.addOption("ufn", "udfFunctionName", true, "Specify the udf function name that must be used in the udf benchmarks"); options.addOption("ufv","udfFunctionValues",true, "The udf argument values comma separated"); @@ -369,14 +374,18 @@ public Main(String[] commandLineArgs) throws Exception { this.asyncEnabled = true; } + if (line.hasOption("proxy")) { + this.useProxyClient = true; + } + args.readPolicy = clientPolicy.readPolicyDefault; args.writePolicy = clientPolicy.writePolicyDefault; args.batchPolicy = clientPolicy.batchPolicyDefault; if (line.hasOption("e")) { - args.writePolicy.expiration = Integer.parseInt(line.getOptionValue("e")); + args.writePolicy.expiration = Integer.parseInt(line.getOptionValue("e")); if (args.writePolicy.expiration < -1) { - throw new Exception("Invalid expiration: "+ args.writePolicy.expiration + " It should be >= -1"); + throw new Exception("Invalid expiration: " + args.writePolicy.expiration + " It should be >= -1"); } } @@ -387,6 +396,13 @@ public Main(String[] commandLineArgs) throws Exception { this.port = 3000; } + // If the Aerospike server's default port (3000) is used and the proxy client is used, + // Reset the port to the proxy server's default port (4000). + if (port == 3000 && useProxyClient) { + System.out.println("Change proxy server port to 4000"); + port = 4000; + } + if (line.hasOption("hosts")) { this.hosts = Host.parseHosts(line.getOptionValue("hosts"), this.port); } @@ -508,7 +524,8 @@ public Main(String[] commandLineArgs) throws Exception { if (line.hasOption("keys")) { this.nKeys = Long.parseLong(line.getOptionValue("keys")); - } else { + } + else { this.nKeys = 100000; } @@ -541,12 +558,13 @@ public Main(String[] commandLineArgs) throws Exception { else if (keyType.equals("I")) { if (Utils.isNumeric(keyList.get(0))) { args.keyType = KeyType.INTEGER; - } else { - throw new Exception("Invalid keyType '"+keyType+"' Key type doesn't match with file content type."); + } + else { + throw new Exception("Invalid keyType '" + keyType + "' Key type doesn't match with file content type."); } } else { - throw new Exception("Invalid keyType: "+keyType); + throw new Exception("Invalid keyType: " + keyType); } } else { @@ -565,7 +583,7 @@ else if (keyType.equals("I")) { if (line.hasOption("objectSpec")) { String[] objectsArr = line.getOptionValue("objectSpec").split(","); args.objectSpec = new DBObjectSpec[objectsArr.length]; - for (int i=0; i 0 && args.writePolicy.socketTimeout < eventPolicy.minTimeout) { + if (args.writePolicy.socketTimeout > 0 && args.writePolicy.socketTimeout < eventPolicy.minTimeout) { eventPolicy.minTimeout = args.writePolicy.socketTimeout; } + if (this.useProxyClient && this.eventLoopType == EventLoopType.DIRECT_NIO) { + // Proxy client requires netty event loops. + if (Epoll.isAvailable()) { + this.eventLoopType = EventLoopType.NETTY_EPOLL; + } + else { + this.eventLoopType = EventLoopType.NETTY_NIO; + } + } + switch (this.eventLoopType) { default: case DIRECT_NIO: { @@ -1148,7 +1176,8 @@ public void runBenchmarks() throws Exception { if (clientPolicy.asyncMaxConnsPerNode < this.asyncMaxCommands) { clientPolicy.asyncMaxConnsPerNode = this.asyncMaxCommands; } - AerospikeClient client = new AerospikeClient(clientPolicy, hosts); + + IAerospikeClient client = AerospikeClientFactory.getClient(clientPolicy, useProxyClient, hosts); try { if (initialize) { @@ -1168,7 +1197,7 @@ public void runBenchmarks() throws Exception { } } else { - AerospikeClient client = new AerospikeClient(clientPolicy, hosts); + IAerospikeClient client = AerospikeClientFactory.getClient(clientPolicy, useProxyClient, hosts); try { if (initialize) { @@ -1185,7 +1214,7 @@ public void runBenchmarks() throws Exception { } } - private void doInserts(AerospikeClient client) throws Exception { + private void doInserts(IAerospikeClient client) throws Exception { ExecutorService es = Executors.newFixedThreadPool(this.nThreads); // Create N insert tasks @@ -1194,7 +1223,7 @@ private void doInserts(AerospikeClient client) throws Exception { long rem = this.nKeys - (keysPerTask * ntasks); long start = this.startKey; - for (long i = 0 ; i < ntasks; i++) { + for (long i = 0; i < ntasks; i++) { long keyCount = (i < rem)? keysPerTask + 1 : keysPerTask; InsertTaskSync it = new InsertTaskSync(client, args, counters, start, keyCount); es.execute(it); @@ -1205,7 +1234,7 @@ private void doInserts(AerospikeClient client) throws Exception { es.shutdownNow(); } - private void doAsyncInserts(AerospikeClient client) throws Exception { + private void doAsyncInserts(IAerospikeClient client) throws Exception { // Generate asyncMaxCommand writes to seed the event loops. // Then start a new command in each command callback. // This effectively throttles new command generation, by only allowing @@ -1240,7 +1269,7 @@ private void collectInsertStats() throws Exception { while (total < this.nKeys) { long time = System.currentTimeMillis(); - int numWrites = this.counters.write.count.getAndSet(0); + int numWrites = this.counters.write.count.getAndSet(0); int timeoutWrites = this.counters.write.timeouts.getAndSet(0); int errorWrites = this.counters.write.errors.getAndSet(0); total += numWrites; @@ -1265,11 +1294,11 @@ private void collectInsertStats() throws Exception { } } - private void doRWTest(AerospikeClient client) throws Exception { + private void doRWTest(IAerospikeClient client) throws Exception { ExecutorService es = Executors.newFixedThreadPool(this.nThreads); RWTask[] tasks = new RWTask[this.nThreads]; - for (int i = 0 ; i < this.nThreads; i++) { + for (int i = 0; i < this.nThreads; i++) { RWTaskSync rt = new RWTaskSync(client, args, counters, this.startKey, this.nKeys); tasks[i] = rt; es.execute(rt); @@ -1279,7 +1308,7 @@ private void doRWTest(AerospikeClient client) throws Exception { es.shutdown(); } - private void doAsyncRWTest(AerospikeClient client) throws Exception { + private void doAsyncRWTest(IAerospikeClient client) throws Exception { // Generate asyncMaxCommand commands to seed the event loops. // Then start a new command in each command callback. // This effectively throttles new command generation, by only allowing @@ -1313,11 +1342,11 @@ private void collectRWStats(RWTask[] tasks) throws Exception { while (true) { long time = System.currentTimeMillis(); - int numWrites = this.counters.write.count.getAndSet(0); + int numWrites = this.counters.write.count.getAndSet(0); int timeoutWrites = this.counters.write.timeouts.getAndSet(0); int errorWrites = this.counters.write.errors.getAndSet(0); - int numReads = this.counters.read.count.getAndSet(0); + int numReads = this.counters.read.count.getAndSet(0); int timeoutReads = this.counters.read.timeouts.getAndSet(0); int errorReads = this.counters.read.errors.getAndSet(0); @@ -1358,7 +1387,7 @@ private void collectRWStats(RWTask[] tasks) throws Exception { } } - if (args.transactionLimit > 0 ) { + if (args.transactionLimit > 0) { transactionTotal += numWrites + timeoutWrites + errorWrites + numReads + timeoutReads + errorReads; if (transactionTotal >= args.transactionLimit) { @@ -1384,7 +1413,7 @@ private void collectRWStats(RWTask[] tasks) throws Exception { } } - private void showBatchNodes(AerospikeClient client) { + private void showBatchNodes(IAerospikeClient client) { if (!batchShowNodes || args.batchSize <= 1) { return; } @@ -1427,14 +1456,15 @@ private static class UsageException extends Exception { private static final long serialVersionUID = 1L; } - private static void printVersion() - { + private static void printVersion() { final Properties properties = new Properties(); try { properties.load(Main.class.getClassLoader().getResourceAsStream("project.properties")); - } catch (Exception e) { + } + catch (Exception e) { System.out.println("None"); - } finally { + } + finally { System.out.println(properties.getProperty("name")); System.out.println("Version " + properties.getProperty("version")); } diff --git a/benchmarks/src/com/aerospike/benchmarks/RWTaskAsync.java b/benchmarks/src/com/aerospike/benchmarks/RWTaskAsync.java index bf66f4d58..bd6c5f0f3 100644 --- a/benchmarks/src/com/aerospike/benchmarks/RWTaskAsync.java +++ b/benchmarks/src/com/aerospike/benchmarks/RWTaskAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.benchmarks; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.Value; @@ -31,7 +31,7 @@ public final class RWTaskAsync extends RWTask { - private final AerospikeClient client; + private final IAerospikeClient client; private final EventLoop eventLoop; private final RandomShift random; private final WriteListener writeListener; @@ -41,7 +41,7 @@ public final class RWTaskAsync extends RWTask { private final boolean useLatency; public RWTaskAsync( - AerospikeClient client, + IAerospikeClient client, EventLoop eventLoop, Arguments args, CounterStore counters, diff --git a/benchmarks/src/com/aerospike/benchmarks/RWTaskSync.java b/benchmarks/src/com/aerospike/benchmarks/RWTaskSync.java index ba67793e3..eb7e67041 100644 --- a/benchmarks/src/com/aerospike/benchmarks/RWTaskSync.java +++ b/benchmarks/src/com/aerospike/benchmarks/RWTaskSync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,8 +16,8 @@ */ package com.aerospike.benchmarks; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.Value; @@ -31,9 +31,9 @@ */ public class RWTaskSync extends RWTask implements Runnable { - private final AerospikeClient client; + private final IAerospikeClient client; - public RWTaskSync(AerospikeClient client, Arguments args, CounterStore counters, long keyStart, long keyCount) { + public RWTaskSync(IAerospikeClient client, Arguments args, CounterStore counters, long keyStart, long keyCount) { super(args, counters, keyStart, keyCount); this.client = client; } diff --git a/client/pom.xml b/client/pom.xml index 970f664ab..0b597acf0 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -93,10 +93,10 @@ make-my-jar-with-dependencies - package - - single - + package + + single + diff --git a/client/src/com/aerospike/client/AerospikeClient.java b/client/src/com/aerospike/client/AerospikeClient.java index 98ce02f6f..fb40f764a 100644 --- a/client/src/com/aerospike/client/AerospikeClient.java +++ b/client/src/com/aerospike/client/AerospikeClient.java @@ -1817,7 +1817,7 @@ public final void getHeader(EventLoop eventLoop, RecordSequenceListener listener */ public final Record operate(WritePolicy policy, Key key, Operation... operations) throws AerospikeException { - OperateArgs args = new OperateArgs(cluster, policy, writePolicyDefault, operatePolicyReadDefault, key, operations); + OperateArgs args = new OperateArgs(policy, writePolicyDefault, operatePolicyReadDefault, key, operations); OperateCommand command = new OperateCommand(cluster, key, args); command.execute(); return command.getRecord(); @@ -1849,7 +1849,7 @@ public final void operate(EventLoop eventLoop, RecordListener listener, WritePol eventLoop = cluster.eventLoops.next(); } - OperateArgs args = new OperateArgs(cluster, policy, writePolicyDefault, operatePolicyReadDefault, key, operations); + OperateArgs args = new OperateArgs(policy, writePolicyDefault, operatePolicyReadDefault, key, operations); AsyncOperate command = new AsyncOperate(cluster, listener, key, args); eventLoop.execute(cluster, command); } diff --git a/client/src/com/aerospike/client/AerospikeException.java b/client/src/com/aerospike/client/AerospikeException.java index 877eb725e..e8d3834df 100644 --- a/client/src/com/aerospike/client/AerospikeException.java +++ b/client/src/com/aerospike/client/AerospikeException.java @@ -189,6 +189,13 @@ public final void setInDoubt(boolean isWrite, int commandSentCounter) { } } + /** + * Sets the inDoubt value to inDoubt. + */ + public void setInDoubt(boolean inDoubt) { + this.inDoubt = inDoubt; + } + /** * Exception thrown when database request expires before completing. */ diff --git a/client/src/com/aerospike/client/async/AsyncCommand.java b/client/src/com/aerospike/client/async/AsyncCommand.java index cf74bf4c2..6b3a60cfe 100644 --- a/client/src/com/aerospike/client/async/AsyncCommand.java +++ b/client/src/com/aerospike/client/async/AsyncCommand.java @@ -120,7 +120,7 @@ protected byte[] getBuffer(int size) { return buffer; } - private final byte[] resizeBuffer(byte[] buffer, int size) { + private byte[] resizeBuffer(byte[] buffer, int size) { if (size > MAX_BUFFER_SIZE) { // Put original buffer back in pool. putBuffer(buffer); @@ -143,7 +143,7 @@ final void validateHeaderSize() { } } - final boolean parseCommandResult() { + boolean parseCommandResult() { if (compressed) { int usize = (int)Buffer.bytesToLong(dataBuffer, 0); byte[] buf = new byte[usize]; diff --git a/client/src/com/aerospike/client/async/AsyncOperate.java b/client/src/com/aerospike/client/async/AsyncOperate.java index 4649a99fb..fa43f3d1e 100644 --- a/client/src/com/aerospike/client/async/AsyncOperate.java +++ b/client/src/com/aerospike/client/async/AsyncOperate.java @@ -28,7 +28,7 @@ public final class AsyncOperate extends AsyncRead { private final OperateArgs args; public AsyncOperate(Cluster cluster, RecordListener listener, Key key, OperateArgs args) { - super(cluster, listener, args.writePolicy, key, args.partition, true); + super(cluster, listener, args.writePolicy, key, args.getPartition(cluster, key), true); this.args = args; } diff --git a/client/src/com/aerospike/client/async/NettyEventLoops.java b/client/src/com/aerospike/client/async/NettyEventLoops.java index d72aa2ad9..8cfc996a6 100644 --- a/client/src/com/aerospike/client/async/NettyEventLoops.java +++ b/client/src/com/aerospike/client/async/NettyEventLoops.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -25,9 +25,14 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.channel.kqueue.KQueueSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.incubator.channel.uring.IOUringEventLoopGroup; +import io.netty.incubator.channel.uring.IOUringSocketChannel; import io.netty.util.concurrent.EventExecutor; /** @@ -134,6 +139,26 @@ private static EventLoopType getEventLoopType(EventLoopGroup group) { throw new AerospikeException("Unexpected EventLoopGroup"); } + /** + * Return SocketChannel class to use in NettyChannelBuilder. + */ + public Class getSocketChannelClass() { + switch (eventLoopType) { + default: + case NETTY_NIO: + return NioSocketChannel.class; + + case NETTY_EPOLL: + return EpollSocketChannel.class; + + case NETTY_KQUEUE: + return KQueueSocketChannel.class; + + case NETTY_IOURING: + return IOUringSocketChannel.class; + } + } + /** * Return corresponding Aerospike event loop given netty event loop. */ diff --git a/client/src/com/aerospike/client/command/Command.java b/client/src/com/aerospike/client/command/Command.java index 85e6986b4..85c0de589 100644 --- a/client/src/com/aerospike/client/command/Command.java +++ b/client/src/com/aerospike/client/command/Command.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -50,8 +50,9 @@ import com.aerospike.client.query.PartitionTracker.NodePartitions; import com.aerospike.client.query.Statement; import com.aerospike.client.util.Packer; +import com.aerospike.client.util.ThreadLocalData; -public abstract class Command { +public class Command { public static final int INFO1_READ = (1 << 0); // Contains a read operation. public static final int INFO1_GET_ALL = (1 << 1); // Get all bins. public static final int INFO1_SHORT_QUERY = (1 << 2); // Short query. @@ -611,9 +612,12 @@ else if (ops != null) { //-------------------------------------------------- public final void setBatchOperate(BatchPolicy policy, List records, BatchNode batch) { - // Estimate full row size - final int[] offsets = batch.offsets; - final int max = batch.offsetsSize; + final BatchRecordIterNative iter = new BatchRecordIterNative(records, batch); + setBatchOperate(policy, iter); + } + + public final void setBatchOperate(BatchPolicy policy, KeyIter iter) { + BatchRecord record; BatchRecord prev = null; begin(); @@ -626,8 +630,7 @@ public final void setBatchOperate(BatchPolicy policy, List iter, + String[] binNames, + Operation[] ops, + BatchAttr attr + ) { // Estimate buffer size. begin(); int fieldCount = 1; @@ -792,11 +801,10 @@ public final void setBatchOperate( dataOffset += FIELD_HEADER_SIZE + 5; + Key key; Key prev = null; - for (int i = 0; i < max; i++) { - Key key = keys[offsets[i]]; - + while ((key = iter.next()) != null) { dataOffset += key.digest.length + 4; // Try reference equality in hope that namespace/set for all keys is set from fixed variables. @@ -848,17 +856,16 @@ else if ((attr.writeAttr & Command.INFO2_DELETE) != 0) { int fieldSizeOffset = dataOffset; writeFieldHeader(0, FieldType.BATCH_INDEX); // Need to update size at end - Buffer.intToBytes(max, dataBuffer, dataOffset); + Buffer.intToBytes(iter.size(), dataBuffer, dataOffset); dataOffset += 4; dataBuffer[dataOffset++] = getBatchFlags(policy); prev = null; + iter.reset(); - for (int i = 0; i < max; i++) { - int index = offsets[i]; - Buffer.intToBytes(index, dataBuffer, dataOffset); + while ((key = iter.next()) != null) { + Buffer.intToBytes(iter.offset(), dataBuffer, dataOffset); dataOffset += 4; - Key key = keys[index]; byte[] digest = key.digest; System.arraycopy(digest, 0, dataBuffer, dataOffset, digest.length); dataOffset += digest.length; @@ -901,10 +908,18 @@ public final void setBatchUDF( byte[] argBytes, BatchAttr attr ) { - // Estimate full row size - final int[] offsets = batch.offsets; - final int max = batch.offsetsSize; + final KeyIterNative iter = new KeyIterNative(keys, batch); + setBatchUDF(policy, iter, packageName, functionName, argBytes, attr); + } + public final void setBatchUDF( + BatchPolicy policy, + KeyIter iter, + String packageName, + String functionName, + byte[] argBytes, + BatchAttr attr + ) { // Estimate buffer size. begin(); int fieldCount = 1; @@ -917,11 +932,10 @@ public final void setBatchUDF( dataOffset += FIELD_HEADER_SIZE + 5; + Key key; Key prev = null; - for (int i = 0; i < max; i++) { - Key key = keys[offsets[i]]; - + while ((key = iter.next()) != null) { dataOffset += key.digest.length + 4; // Try reference equality in hope that namespace/set for all keys is set from fixed variables. @@ -955,17 +969,16 @@ public final void setBatchUDF( int fieldSizeOffset = dataOffset; writeFieldHeader(0, FieldType.BATCH_INDEX); // Need to update size at end - Buffer.intToBytes(max, dataBuffer, dataOffset); + Buffer.intToBytes(iter.size(), dataBuffer, dataOffset); dataOffset += 4; dataBuffer[dataOffset++] = getBatchFlags(policy); prev = null; + iter.reset(); - for (int i = 0; i < max; i++) { - int index = offsets[i]; - Buffer.intToBytes(index, dataBuffer, dataOffset); + while ((key = iter.next()) != null) { + Buffer.intToBytes(iter.offset(), dataBuffer, dataOffset); dataOffset += 4; - Key key = keys[index]; byte[] digest = key.digest; System.arraycopy(digest, 0, dataBuffer, dataOffset, digest.length); dataOffset += digest.length; @@ -1547,6 +1560,16 @@ else if (binNames != null && (isNew || filter == null)) { //-------------------------------------------------- private final int estimateKeySize(Policy policy, Key key) { + int fieldCount = estimateKeySize(key); + + if (policy.sendKey) { + dataOffset += key.userKey.estimateSize() + FIELD_HEADER_SIZE + 1; + fieldCount++; + } + return fieldCount; + } + + protected final int estimateKeySize(Key key) { int fieldCount = 0; if (key.namespace != null) { @@ -1561,11 +1584,6 @@ private final int estimateKeySize(Policy policy, Key key) { dataOffset += key.digest.length + FIELD_HEADER_SIZE; fieldCount++; - - if (policy.sendKey) { - dataOffset += key.userKey.estimateSize() + FIELD_HEADER_SIZE + 1; - fieldCount++; - } return fieldCount; } @@ -1853,6 +1871,14 @@ private final void writeHeaderReadHeader(Policy policy, int readAttr, int fieldC } private final void writeKey(Policy policy, Key key) { + writeKey(key); + + if (policy.sendKey) { + writeField(key.userKey, FieldType.KEY); + } + } + + protected final void writeKey(Key key) { // Write key into buffer. if (key.namespace != null) { writeField(key.namespace, FieldType.NAMESPACE); @@ -1863,10 +1889,6 @@ private final void writeKey(Policy policy, Key key) { } writeField(key.digest, FieldType.DIGEST_RIPE); - - if (policy.sendKey) { - writeField(key.userKey, FieldType.KEY); - } } private final int writeReadOnlyOperations(Operation[] ops, int readAttr) { @@ -1988,11 +2010,11 @@ public final void writeExpHeader(int size) { writeFieldHeader(size, FieldType.FILTER_EXP); } - private final void begin() { + protected final void begin() { dataOffset = MSG_TOTAL_HEADER_SIZE; } - private final void end() { + protected final void end() { // Write total size of message which is the current offset. long proto = (dataOffset - 8) | (CL_MSG_VERSION << 56) | (AS_MSG_TYPE << 48); Buffer.longToBytes(proto, dataBuffer, 0); @@ -2023,7 +2045,13 @@ private final void compress(Policy policy) { } } - protected abstract void sizeBuffer(); + protected void sizeBuffer() { + dataBuffer = ThreadLocalData.getBuffer(); + + if (dataOffset > dataBuffer.length) { + dataBuffer = ThreadLocalData.resizeBuffer(dataOffset); + } + } //-------------------------------------------------- // Response Parsing @@ -2132,7 +2160,80 @@ public static boolean batchInDoubt(boolean isWrite, int commandSentCounter) { return isWrite && commandSentCounter > 1; } - private static class OpResults extends ArrayList { + public static class OpResults extends ArrayList { private static final long serialVersionUID = 1L; } + + public interface KeyIter { + int size(); + T next(); + int offset(); + void reset(); + } + + private static class BatchRecordIterNative extends BaseIterNative { + private final List records; + + public BatchRecordIterNative(List records, BatchNode batch) { + super(batch); + this.records = records; + } + + @Override + public BatchRecord get(int offset) { + return records.get(offset); + } + } + + private static class KeyIterNative extends BaseIterNative { + private final Key[] keys; + + public KeyIterNative(Key[] keys, BatchNode batch) { + super(batch); + this.keys = keys; + } + + @Override + public Key get(int offset) { + return keys[offset]; + } + } + + private static abstract class BaseIterNative implements KeyIter { + private final int size; + private final int[] offsets; + private int offset; + private int index; + + public BaseIterNative(BatchNode batch) { + this.size = batch.offsetsSize; + this.offsets = batch.offsets; + } + + @Override + public int size() { + return size; + } + + @Override + public T next() { + if (index >= size) { + return null; + } + offset = offsets[index++]; + return get(offset); + } + + abstract T get(int offset); + + @Override + public int offset() { + return offset; + } + + @Override + public void reset() { + index = 0; + } + } } diff --git a/client/src/com/aerospike/client/command/OperateArgs.java b/client/src/com/aerospike/client/command/OperateArgs.java index 3e657842b..13c77d2cb 100644 --- a/client/src/com/aerospike/client/command/OperateArgs.java +++ b/client/src/com/aerospike/client/command/OperateArgs.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -25,14 +25,12 @@ public final class OperateArgs { public final WritePolicy writePolicy; public final Operation[] operations; - public final Partition partition; public final int size; public final int readAttr; public final int writeAttr; public final boolean hasWrite; public OperateArgs( - Cluster cluster, WritePolicy policy, WritePolicy writeDefault, WritePolicy readDefault, @@ -114,12 +112,14 @@ public OperateArgs( wattr |= Command.INFO2_RESPOND_ALL_OPS; } writeAttr = wattr; + } - if (write) { - partition = Partition.write(cluster, writePolicy, key); + public Partition getPartition(Cluster cluster, Key key) { + if (hasWrite) { + return Partition.write(cluster, writePolicy, key); } else { - partition = Partition.read(cluster, writePolicy, key); + return Partition.read(cluster, writePolicy, key); } } } diff --git a/client/src/com/aerospike/client/command/OperateCommand.java b/client/src/com/aerospike/client/command/OperateCommand.java index 741755acd..e1f3331db 100644 --- a/client/src/com/aerospike/client/command/OperateCommand.java +++ b/client/src/com/aerospike/client/command/OperateCommand.java @@ -26,7 +26,7 @@ public final class OperateCommand extends ReadCommand { private final OperateArgs args; public OperateCommand(Cluster cluster, Key key, OperateArgs args) { - super(cluster, args.writePolicy, key, args.partition, true); + super(cluster, args.writePolicy, key, args.getPartition(cluster, key), true); this.args = args; } diff --git a/client/src/com/aerospike/client/command/SyncCommand.java b/client/src/com/aerospike/client/command/SyncCommand.java index 1b1012bb7..19d78c305 100644 --- a/client/src/com/aerospike/client/command/SyncCommand.java +++ b/client/src/com/aerospike/client/command/SyncCommand.java @@ -280,15 +280,6 @@ public void resetDeadline(long startTime) { deadline += elapsed; } - @Override - protected void sizeBuffer() { - dataBuffer = ThreadLocalData.getBuffer(); - - if (dataOffset > dataBuffer.length) { - dataBuffer = ThreadLocalData.resizeBuffer(dataOffset); - } - } - protected void sizeBuffer(int size) { if (size > dataBuffer.length) { dataBuffer = ThreadLocalData.resizeBuffer(size); diff --git a/client/src/com/aerospike/client/query/Filter.java b/client/src/com/aerospike/client/query/Filter.java index 8ab964ebe..f37cefb5f 100644 --- a/client/src/com/aerospike/client/query/Filter.java +++ b/client/src/com/aerospike/client/query/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -210,12 +210,16 @@ public static Filter geoContains(String name, IndexCollectionType type, String p private final Value end; private Filter(String name, IndexCollectionType colType, int valType, Value begin, Value end, CTX[] ctx) { + this(name, colType, valType, begin, end, (ctx != null && ctx.length > 0) ? Pack.pack(ctx) : null); + } + + Filter(String name, IndexCollectionType colType, int valType, Value begin, Value end, byte[] packedCtx) { this.name = name; this.colType = colType; this.valType = valType; this.begin = begin; this.end = end; - this.packedCtx = (ctx != null && ctx.length > 0)? Pack.pack(ctx) : null; + this.packedCtx = packedCtx; } /** @@ -261,6 +265,46 @@ public IndexCollectionType getCollectionType() { return colType; } + /** + * Filter name. + * For internal use only. + */ + public String getName() { + return name; + } + + /** + * Index collection type. + * For internal use only. + */ + public IndexCollectionType getColType() { + return colType; + } + + /** + * Filter begin value. + * For internal use only. + */ + public Value getBegin() { + return begin; + } + + /** + * Filter begin value. + * For internal use only. + */ + public Value getEnd() { + return end; + } + + /** + * Filter Value type. + * For internal use only. + */ + public int getValType() { + return valType; + } + /** * Retrieve packed Context. * For internal use only. diff --git a/client/src/com/aerospike/client/query/PartitionFilter.java b/client/src/com/aerospike/client/query/PartitionFilter.java index 6e0a687cc..aebff5b0f 100644 --- a/client/src/com/aerospike/client/query/PartitionFilter.java +++ b/client/src/com/aerospike/client/query/PartitionFilter.java @@ -61,6 +61,18 @@ public static PartitionFilter after(Key key) { return new PartitionFilter(key.digest); } + /** + * Return records after the digest in partition containing the digest. + * Note that digest order is not the same as userKey order. This method + * only works for scan or query with null filter (primary index query). + * This method does not work for a secondary index query. + * + * @param digest return records after this digest + */ + public static PartitionFilter after(byte[] digest) { + return new PartitionFilter(digest); + } + /** * Filter by partition range. * @@ -148,4 +160,11 @@ public void setPartitions(PartitionStatus[] partitions) { public boolean isDone() { return done; } + + /** + * Indicates if the entire filter requires a retry after a failed attempt. + */ + public boolean isRetry() { + return retry; + } } diff --git a/client/src/com/aerospike/client/query/PartitionTracker.java b/client/src/com/aerospike/client/query/PartitionTracker.java index 6def91fd8..229b068bf 100644 --- a/client/src/com/aerospike/client/query/PartitionTracker.java +++ b/client/src/com/aerospike/client/query/PartitionTracker.java @@ -54,24 +54,34 @@ public final class PartitionTracker { private long deadline; public PartitionTracker(ScanPolicy policy, Node[] nodes) { - this((Policy)policy, nodes); + this((Policy)policy, nodes.length); + setMaxRecords(policy.maxRecords); + } + + public PartitionTracker(ScanPolicy policy, int nodeCapacity) { + this((Policy)policy, nodeCapacity); setMaxRecords(policy.maxRecords); } public PartitionTracker(QueryPolicy policy, Statement stmt, Node[] nodes) { - this((Policy)policy, nodes); + this((Policy)policy, nodes.length); + setMaxRecords(policy, stmt); + } + + public PartitionTracker(QueryPolicy policy, Statement stmt, int nodeCapacity) { + this((Policy)policy, nodeCapacity); setMaxRecords(policy, stmt); } - private PartitionTracker(Policy policy, Node[] nodes) { + private PartitionTracker(Policy policy, int nodeCapacity) { this.partitionBegin = 0; - this.nodeCapacity = nodes.length; + this.nodeCapacity = nodeCapacity; this.nodeFilter = null; this.partitionFilter = null; this.replica = policy.replica; // Create initial partition capacity for each node as average + 25%. - int ppn = Node.PARTITIONS / nodes.length; + int ppn = Node.PARTITIONS / nodeCapacity; ppn += ppn >>> 2; this.partitionsCapacity = ppn; this.partitions = initPartitions(Node.PARTITIONS, null); @@ -103,17 +113,30 @@ public PartitionTracker(ScanPolicy policy, Node[] nodes, PartitionFilter filter) this((Policy)policy, nodes, filter, policy.maxRecords); } + public PartitionTracker(ScanPolicy policy, int nodeCapacity, PartitionFilter filter) { + this((Policy)policy, nodeCapacity, filter, policy.maxRecords); + } + public PartitionTracker(QueryPolicy policy, Statement stmt, Node[] nodes, PartitionFilter filter) { - this((Policy)policy, nodes, filter, (stmt.maxRecords > 0)? stmt.maxRecords : policy.maxRecords); + this((Policy)policy, nodes, filter, (stmt.maxRecords > 0) ? stmt.maxRecords : policy.maxRecords); + } + + public PartitionTracker(QueryPolicy policy, Statement stmt, int nodeCapacity, PartitionFilter filter) { + this((Policy)policy, nodeCapacity, filter, (stmt.maxRecords > 0) ? + stmt.maxRecords : policy.maxRecords); } private PartitionTracker(Policy policy, Node[] nodes, PartitionFilter filter, long maxRecords) { + this(policy, nodes.length, filter, maxRecords); + } + + private PartitionTracker(Policy policy, int nodeCapacity, PartitionFilter filter, long maxRecords) { // Validate here instead of initial PartitionFilter constructor because total number of // cluster partitions may change on the server and PartitionFilter will never have access // to Cluster instance. Use fixed number of partitions for now. - if (! (filter.begin >= 0 && filter.begin < Node.PARTITIONS)) { + if (!(filter.begin >= 0 && filter.begin < Node.PARTITIONS)) { throw new AerospikeException(ResultCode.PARAMETER_ERROR, "Invalid partition begin " + filter.begin + - ". Valid range: 0-" + (Node.PARTITIONS-1)); + ". Valid range: 0-" + (Node.PARTITIONS - 1)); } if (filter.count <= 0) { @@ -127,7 +150,7 @@ private PartitionTracker(Policy policy, Node[] nodes, PartitionFilter filter, lo setMaxRecords(maxRecords); this.partitionBegin = filter.begin; - this.nodeCapacity = nodes.length; + this.nodeCapacity = nodeCapacity; this.nodeFilter = null; this.partitionsCapacity = filter.count; this.replica = policy.replica; @@ -299,12 +322,47 @@ public void partitionUnavailable(NodePartitions nodePartitions, int partitionId) nodePartitions.partsUnavailable++; } + /** + * Update the last seen digest for a partition. + * Internal use only. + * + * @param partitionId partition id + * @param digest the last seen digest. + */ + void setDigest(int partitionId, byte[] digest) { + for (PartitionStatus ps : partitions) { + if (ps.id == partitionId) { + ps.digest = digest; + } + } + } + public void setDigest(NodePartitions nodePartitions, Key key) { int partitionId = Partition.getPartitionId(key.digest); partitions[partitionId - partitionBegin].digest = key.digest; nodePartitions.recordCount++; } + /** + * Update the last seen value for a partition. + * Internal use only. + * + * @param partitionId partition id + * @param digest the record digest + * @param bval the last seen value. + * @param retry indicates if this partition should be retried. + */ + void setLast(int partitionId, byte[] digest, long bval, boolean retry) { + int pIndex = partitionId - partitionBegin; + if (pIndex < partitions.length) { + PartitionStatus ps = partitions[pIndex]; + assert ps.id == partitionId; + ps.bval = bval; + ps.digest = digest; + ps.retry = retry; + } + } + public void setLast(NodePartitions nodePartitions, Key key, long bval) { int partitionId = Partition.getPartitionId(key.digest); PartitionStatus ps = partitions[partitionId - partitionBegin]; @@ -318,6 +376,10 @@ public boolean allowRecord() { } public boolean isComplete(Cluster cluster, Policy policy) { + return isComplete(cluster.hasPartitionQuery, policy, nodePartitionsList); + } + + public boolean isComplete(boolean hasPartitionQuery, Policy policy, List nodePartitionsList) { long recCount = 0; int partsUnavailable = 0; @@ -333,6 +395,7 @@ public boolean isComplete(Cluster cluster, Policy policy) { if (partsUnavailable == 0) { if (maxRecords == 0) { if (partitionFilter != null) { + partitionFilter.retry = false; partitionFilter.done = true; } } @@ -347,7 +410,7 @@ else if (iteration > 1) { } } else { - if (cluster.hasPartitionQuery) { + if (hasPartitionQuery) { // Server version >= 6.0 will return all records for each node up to // that node's max. If node's record count reached max, there still // may be records available for that node. diff --git a/client/src/com/aerospike/client/query/RecordSet.java b/client/src/com/aerospike/client/query/RecordSet.java index ec40c6a1a..ad36a5e65 100644 --- a/client/src/com/aerospike/client/query/RecordSet.java +++ b/client/src/com/aerospike/client/query/RecordSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -30,7 +30,7 @@ * Multiple threads will retrieve records from the server nodes and put these records on the queue. * The single user thread consumes these records from the queue. */ -public final class RecordSet implements Iterable, Closeable { +public class RecordSet implements Iterable, Closeable { public static final KeyRecord END = new KeyRecord(null, null); private final IQueryExecutor executor; @@ -46,6 +46,14 @@ protected RecordSet(IQueryExecutor executor, int capacity) { this.queue = new ArrayBlockingQueue(capacity); } + /** + * For internal use only. + */ + protected RecordSet() { + this.executor = null; + this.queue = null; + } + //------------------------------------------------------- // Record traversal methods //------------------------------------------------------- @@ -54,9 +62,9 @@ protected RecordSet(IQueryExecutor executor, int capacity) { * Retrieve next record. This method will block until a record is retrieved * or the query is cancelled. * - * @return whether record exists - if false, no more records are available + * @return whether record exists - if false, no more records are available */ - public final boolean next() throws AerospikeException { + public boolean next() throws AerospikeException { if (! valid) { executor.checkForException(); return false; @@ -87,7 +95,7 @@ record = queue.take(); /** * Close query. */ - public final void close() { + public void close() { valid = false; // Check if more records are available. @@ -112,14 +120,14 @@ public Iterator iterator() { /** * Get record's unique identifier. */ - public final Key getKey() { + public Key getKey() { return record.key; } /** * Get record's header and bin data. */ - public final Record getRecord() { + public Record getRecord() { return record.record; } @@ -158,7 +166,7 @@ protected final boolean put(KeyRecord record) { /** * Abort retrieval with end token. */ - protected final void abort() { + protected void abort() { valid = false; queue.clear(); diff --git a/client/src/com/aerospike/client/query/ResultSet.java b/client/src/com/aerospike/client/query/ResultSet.java index f643085bf..842e13721 100644 --- a/client/src/com/aerospike/client/query/ResultSet.java +++ b/client/src/com/aerospike/client/query/ResultSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -29,7 +29,7 @@ * Multiple threads will retrieve results from the server nodes and put these results on the queue. * The single user thread consumes these results from the queue. */ -public final class ResultSet implements Iterable, Closeable { +public class ResultSet implements Iterable, Closeable { public static final Object END = new Object(); private final QueryAggregateExecutor executor; @@ -45,6 +45,14 @@ protected ResultSet(QueryAggregateExecutor executor, int capacity) { this.queue = new ArrayBlockingQueue(capacity); } + /** + * For internal use only. + */ + protected ResultSet() { + this.executor = null; + this.queue = null; + } + //------------------------------------------------------- // Result traversal methods //------------------------------------------------------- @@ -53,10 +61,10 @@ protected ResultSet(QueryAggregateExecutor executor, int capacity) { * Retrieve next result. This method will block until a result is retrieved * or the query is cancelled. * - * @return whether result exists - if false, no more results are available + * @return whether result exists - if false, no more results are available */ - public final boolean next() throws AerospikeException { - if (! valid) { + public boolean next() throws AerospikeException { + if (!valid) { executor.checkForException(); return false; } @@ -84,7 +92,7 @@ public final boolean next() throws AerospikeException { /** * Close query. */ - public final void close() { + public void close() { valid = false; // Check if more results are available. @@ -109,7 +117,7 @@ public Iterator iterator() { /** * Get result. */ - public final Object getObject() { + public Object getObject() { return row; } @@ -120,8 +128,8 @@ public final Object getObject() { /** * Put object on the queue. */ - public final boolean put(Object object) { - if (! valid) { + public boolean put(Object object) { + if (!valid) { return false; } @@ -146,13 +154,13 @@ public final boolean put(Object object) { /** * Abort retrieval with end token. */ - protected final void abort() { + protected void abort() { valid = false; queue.clear(); // Send end command to transaction thread. // It's critical that the end offer succeeds. - while (! queue.offer(END)) { + while (!queue.offer(END)) { // Queue must be full. Remove one item to make room. if (queue.poll() == null) { // Can't offer or poll. Nothing further can be done. diff --git a/client/src/com/aerospike/client/task/ExecuteTask.java b/client/src/com/aerospike/client/task/ExecuteTask.java index 40fb8f428..b2d82fd2e 100644 --- a/client/src/com/aerospike/client/task/ExecuteTask.java +++ b/client/src/com/aerospike/client/task/ExecuteTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -24,9 +24,9 @@ import com.aerospike.client.query.Statement; /** - * Task used to poll for long running server execute job completion. + * Task used to poll for long-running server execute job completion. */ -public final class ExecuteTask extends Task { +public class ExecuteTask extends Task { private final long taskId; private final boolean scan; @@ -34,9 +34,25 @@ public final class ExecuteTask extends Task { * Initialize task with fields needed to query server nodes. */ public ExecuteTask(Cluster cluster, Policy policy, Statement statement, long taskId) { + this(cluster, policy, taskId, statement.isScan()); + } + + /** + * Initialize task with fields needed to query server nodes. + */ + public ExecuteTask(Cluster cluster, Policy policy, long taskId, boolean isScan) { super(cluster, policy); this.taskId = taskId; - this.scan = statement.isScan(); + this.scan = isScan; + } + + /** + * Initialize task with fields needed to query server nodes. + */ + protected ExecuteTask(long taskId, boolean isScan) { + super(null, new Policy()); + this.taskId = taskId; + this.scan = isScan; } /** diff --git a/examples/pom.xml b/examples/pom.xml index 52ccc8ab3..843526ce0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -19,6 +19,11 @@ aerospike-client + + com.aerospike + aerospike-proxy-client + + io.netty netty-transport @@ -62,24 +67,26 @@ maven-compiler-plugin - maven-assembly-plugin - - - jar-with-dependencies - - - - com.aerospike.examples.Main - - - + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 - make-my-jar-with-dependencies - package - - single - + + shade + + + true + jar-with-dependencies + + + + + com.aerospike.examples.Main + + + + diff --git a/examples/src/com/aerospike/examples/Add.java b/examples/src/com/aerospike/examples/Add.java index 8d27359cf..a82131924 100644 --- a/examples/src/com/aerospike/examples/Add.java +++ b/examples/src/com/aerospike/examples/Add.java @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -32,7 +32,7 @@ public Add(Console console) { * Add integer values. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "addkey"); String binName = "addbin"; diff --git a/examples/src/com/aerospike/examples/Append.java b/examples/src/com/aerospike/examples/Append.java index 01e37c5d1..0015a6fb1 100644 --- a/examples/src/com/aerospike/examples/Append.java +++ b/examples/src/com/aerospike/examples/Append.java @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; @@ -31,7 +31,7 @@ public Append(Console console) { * Append string to an existing string. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "appendkey"); String binName = "appendbin"; diff --git a/examples/src/com/aerospike/examples/AsyncBatch.java b/examples/src/com/aerospike/examples/AsyncBatch.java index 84786a62f..ab09dec23 100644 --- a/examples/src/com/aerospike/examples/AsyncBatch.java +++ b/examples/src/com/aerospike/examples/AsyncBatch.java @@ -19,10 +19,10 @@ import java.util.ArrayList; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.BatchRead; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Log.Level; import com.aerospike.client.Record; @@ -38,7 +38,7 @@ public class AsyncBatch extends AsyncExample { - private AerospikeClient client; + private IAerospikeClient client; private EventLoop eventLoop; private final String keyPrefix = "batchkey"; private final String valuePrefix = "batchvalue"; @@ -50,7 +50,7 @@ public class AsyncBatch extends AsyncExample { * Asynchronous batch examples. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { + public void runExample(IAerospikeClient client, EventLoop eventLoop) { this.client = client; this.eventLoop = eventLoop; this.binName = "batchbin"; diff --git a/examples/src/com/aerospike/examples/AsyncExample.java b/examples/src/com/aerospike/examples/AsyncExample.java index f2ca24c92..366669953 100644 --- a/examples/src/com/aerospike/examples/AsyncExample.java +++ b/examples/src/com/aerospike/examples/AsyncExample.java @@ -19,9 +19,10 @@ import java.lang.reflect.Constructor; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.async.EventLoop; +import com.aerospike.client.async.EventLoopType; import com.aerospike.client.async.EventLoops; import com.aerospike.client.async.EventPolicy; import com.aerospike.client.async.NettyEventLoops; @@ -29,8 +30,11 @@ import com.aerospike.client.policy.ClientPolicy; import com.aerospike.client.policy.Policy; import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.AerospikeClientFactory; +import com.aerospike.client.util.Util; import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -45,6 +49,16 @@ public static void runExamples(Console console, Parameters params, List eventPolicy.maxCommandsInProcess = params.maxCommandsInProcess; eventPolicy.maxCommandsInQueue = params.maxCommandsInQueue; + if (params.useProxyClient && params.eventLoopType == EventLoopType.DIRECT_NIO) { + // Proxy client requires netty event loops. + if (Epoll.isAvailable()) { + params.eventLoopType = EventLoopType.NETTY_EPOLL; + } + else { + params.eventLoopType = EventLoopType.NETTY_NIO; + } + } + EventLoops eventLoops; switch (params.eventLoopType) { @@ -92,7 +106,7 @@ public static void runExamples(Console console, Parameters params, List Host[] hosts = Host.parseHosts(params.host, params.port); - AerospikeClient client = new AerospikeClient(policy, hosts); + IAerospikeClient client = AerospikeClientFactory.getClient(policy, params.useProxyClient, hosts); try { EventLoop eventLoop = eventLoops.get(0); @@ -101,6 +115,11 @@ public static void runExamples(Console console, Parameters params, List for (String exampleName : examples) { runExample(exampleName, client, eventLoop, params, console); } + + // TODO: Remove sleep after adding functionality to wait until async commands complete. + System.out.println("Sleep 2 seconds"); + Util.sleep(2000); + System.out.println("Sleep end"); } finally { client.close(); @@ -114,7 +133,7 @@ public static void runExamples(Console console, Parameters params, List /** * Run asynchronous client example. */ - public static void runExample(String exampleName, AerospikeClient client, EventLoop eventLoop, Parameters params, Console console) throws Exception { + public static void runExample(String exampleName, IAerospikeClient client, EventLoop eventLoop, Parameters params, Console console) throws Exception { String fullName = "com.aerospike.examples." + exampleName; Class cls = Class.forName(fullName); @@ -123,8 +142,8 @@ public static void runExample(String exampleName, AerospikeClient client, EventL AsyncExample example = (AsyncExample)ctor.newInstance(); example.console = console; example.params = params; - example.writePolicy = client.writePolicyDefault; - example.policy = client.readPolicyDefault; + example.writePolicy = client.getWritePolicyDefault(); + example.policy = client.getReadPolicyDefault(); example.run(client, eventLoop); } else { @@ -138,7 +157,7 @@ public static void runExample(String exampleName, AerospikeClient client, EventL protected Policy policy; private boolean completed; - public void run(AerospikeClient client, EventLoop eventLoop) { + public void run(IAerospikeClient client, EventLoop eventLoop) { // Most async examples no longer wait for completion, so // these examples are run in parallel with intertwined log // messages. It's done that way because most applications @@ -169,5 +188,5 @@ protected synchronized void notifyComplete() { super.notify(); } - public abstract void runExample(AerospikeClient client, EventLoop eventLoop); + public abstract void runExample(IAerospikeClient client, EventLoop eventLoop); } diff --git a/examples/src/com/aerospike/examples/AsyncPutGet.java b/examples/src/com/aerospike/examples/AsyncPutGet.java index 527916ab0..405a12494 100644 --- a/examples/src/com/aerospike/examples/AsyncPutGet.java +++ b/examples/src/com/aerospike/examples/AsyncPutGet.java @@ -19,9 +19,9 @@ import java.io.IOException; import java.net.ConnectException; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.async.EventLoop; @@ -33,7 +33,7 @@ public class AsyncPutGet extends AsyncExample { * Asynchronously write and read a bin using alternate methods. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { + public void runExample(IAerospikeClient client, EventLoop eventLoop) { Key key = new Key(params.namespace, params.set, "putgetkey"); Bin bin = new Bin("putgetbin", "value"); @@ -42,7 +42,7 @@ public void runExample(AerospikeClient client, EventLoop eventLoop) { } // Inline asynchronous put/get calls. - private void runPutGetInline(final AerospikeClient client, final EventLoop eventLoop, final Key key, final Bin bin) { + private void runPutGetInline(final IAerospikeClient client, final EventLoop eventLoop, final Key key, final Bin bin) { console.info("Put inline: namespace=%s set=%s key=%s value=%s", key.namespace, key.setName, key.userKey, bin.value); @@ -74,19 +74,19 @@ public void onFailure(AerospikeException e) { } // Asynchronous put/get calls with retry. - private void runPutGetWithRetry(AerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { + private void runPutGetWithRetry(IAerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { console.info("Put with retry: namespace=%s set=%s key=%s value=%s", key.namespace, key.setName, key.userKey, bin.value); client.put(eventLoop, new WriteHandler(client, eventLoop, key, bin), writePolicy, key, bin); } private class WriteHandler implements WriteListener { - private final AerospikeClient client; + private final IAerospikeClient client; private final EventLoop eventLoop; private final Key key; private final Bin bin; private int failCount = 0; - public WriteHandler(AerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { + public WriteHandler(IAerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { this.client = client; this.eventLoop = eventLoop; this.key = key; @@ -128,13 +128,13 @@ public void onFailure(AerospikeException e) { } private class ReadHandler implements RecordListener { - private final AerospikeClient client; + private final IAerospikeClient client; private final EventLoop eventLoop; private final Key key; private final Bin bin; private int failCount = 0; - public ReadHandler(AerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { + public ReadHandler(IAerospikeClient client, EventLoop eventLoop, Key key, Bin bin) { this.client = client; this.eventLoop = eventLoop; this.key = key; diff --git a/examples/src/com/aerospike/examples/AsyncQuery.java b/examples/src/com/aerospike/examples/AsyncQuery.java index 0a0194405..9cfa2e9a8 100644 --- a/examples/src/com/aerospike/examples/AsyncQuery.java +++ b/examples/src/com/aerospike/examples/AsyncQuery.java @@ -18,9 +18,9 @@ import java.util.concurrent.atomic.AtomicInteger; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,22 +40,29 @@ public class AsyncQuery extends AsyncExample { * Asynchronous query example. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { + public void runExample(IAerospikeClient client, EventLoop eventLoop) { String indexName = "asqindex"; String keyPrefix = "asqkey"; String binName = "asqbin"; int size = 50; - createIndex(client, indexName, binName); + // Proxy client does not support createIndex(), so must assume + // index already created to run this test. + if (! params.useProxyClient) { + createIndex(client, indexName, binName); + } runQueryExample(client, eventLoop, keyPrefix, binName, size); // Wait until query finishes before dropping index. waitTillComplete(); - client.dropIndex(policy, params.namespace, params.set, indexName); + + // Do not drop index because after native client tests run, the proxy + // client tests need the index to exist. + //client.dropIndex(policy, params.namespace, params.set, indexName); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, String indexName, String binName ) { @@ -77,7 +84,7 @@ private void createIndex( } private void runQueryExample( - final AerospikeClient client, + final IAerospikeClient client, final EventLoop eventLoop, final String keyPrefix, final String binName, @@ -110,7 +117,7 @@ public void onFailure(AerospikeException e) { } } - private void runQuery(AerospikeClient client, EventLoop eventLoop, final String binName) { + private void runQuery(IAerospikeClient client, EventLoop eventLoop, final String binName) { int begin = 26; int end = 34; diff --git a/examples/src/com/aerospike/examples/AsyncScan.java b/examples/src/com/aerospike/examples/AsyncScan.java index b82be3b27..704debed0 100644 --- a/examples/src/com/aerospike/examples/AsyncScan.java +++ b/examples/src/com/aerospike/examples/AsyncScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.async.EventLoop; @@ -33,7 +33,7 @@ public class AsyncScan extends AsyncExample { * Asynchronous scan example. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { + public void runExample(IAerospikeClient client, EventLoop eventLoop) { console.info("Asynchronous scan: namespace=" + params.namespace + " set=" + params.set); recordCount = 0; final long begin = System.currentTimeMillis(); diff --git a/examples/src/com/aerospike/examples/AsyncScanPage.java b/examples/src/com/aerospike/examples/AsyncScanPage.java index 8ead49f2c..a678e19d0 100644 --- a/examples/src/com/aerospike/examples/AsyncScanPage.java +++ b/examples/src/com/aerospike/examples/AsyncScanPage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.async.EventLoop; @@ -37,7 +37,7 @@ public class AsyncScanPage extends AsyncExample { * Asynchronous scan example. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { + public void runExample(IAerospikeClient client, EventLoop eventLoop) { console.info("Write " + size + " records."); WriteListener listener = new WriteListener() { @@ -75,7 +75,7 @@ public void onFailure(AerospikeException e) { waitTillComplete(); } - private void runScan(AerospikeClient client, EventLoop eventLoop) { + private void runScan(IAerospikeClient client, EventLoop eventLoop) { int pageSize = 30; console.info("Scan max " + pageSize + " records."); diff --git a/examples/src/com/aerospike/examples/AsyncUserDefinedFunction.java b/examples/src/com/aerospike/examples/AsyncUserDefinedFunction.java index 830d3e594..c8a48b548 100644 --- a/examples/src/com/aerospike/examples/AsyncUserDefinedFunction.java +++ b/examples/src/com/aerospike/examples/AsyncUserDefinedFunction.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.Value; @@ -31,17 +31,23 @@ public class AsyncUserDefinedFunction extends AsyncExample { * Asynchronous query example. */ @Override - public void runExample(AerospikeClient client, EventLoop eventLoop) { - register(client); + public void runExample(IAerospikeClient client, EventLoop eventLoop) { + // Register is not supported in the proxy client. To run this example with the proxy client, + // first run example with native client (which supports register) and then run proxy client. + if (! params.useProxyClient) { + register(client); + } writeUsingUdfAsync(client, eventLoop); } - private void register(AerospikeClient client) { - RegisterTask task = client.register(params.policy, "udf/record_example.lua", "record_example.lua", Language.LUA); + private void register(IAerospikeClient client) { + String filename = "record_example.lua"; + console.info("Register: " + filename); + RegisterTask task = client.register(params.policy, "udf/record_example.lua", filename, Language.LUA); task.waitTillComplete(); } - private void writeUsingUdfAsync(final AerospikeClient client, final EventLoop eventLoop) { + private void writeUsingUdfAsync(final IAerospikeClient client, final EventLoop eventLoop) { final Key key = new Key(params.namespace, params.set, "audfkey1"); final Bin bin = new Bin("audfbin1", "string value"); diff --git a/examples/src/com/aerospike/examples/Batch.java b/examples/src/com/aerospike/examples/Batch.java index 22a941305..91919983b 100644 --- a/examples/src/com/aerospike/examples/Batch.java +++ b/examples/src/com/aerospike/examples/Batch.java @@ -19,9 +19,9 @@ import java.util.ArrayList; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.BatchRead; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Log.Level; import com.aerospike.client.Record; @@ -36,7 +36,7 @@ public Batch(Console console) { * Batch multiple gets in one call to the server. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String keyPrefix = "batchkey"; String valuePrefix = "batchvalue"; String binName = "batchbin"; @@ -53,7 +53,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti * Write records individually. */ private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -75,7 +75,7 @@ private void writeRecords( * Check existence of records in one batch. */ private void batchExists ( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, int size @@ -100,7 +100,7 @@ private void batchExists ( * Read records in one batch. */ private void batchReads ( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -137,7 +137,7 @@ private void batchReads ( * Read record header data in one batch. */ private void batchReadHeaders ( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, int size @@ -176,7 +176,7 @@ private void batchReadHeaders ( * This requires Aerospike Server version >= 3.6.0. */ private void batchReadComplex ( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName diff --git a/examples/src/com/aerospike/examples/BatchOperate.java b/examples/src/com/aerospike/examples/BatchOperate.java index b1e96284d..de95e2853 100644 --- a/examples/src/com/aerospike/examples/BatchOperate.java +++ b/examples/src/com/aerospike/examples/BatchOperate.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -19,13 +19,13 @@ import java.util.ArrayList; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.BatchDelete; import com.aerospike.client.BatchRead; import com.aerospike.client.BatchRecord; import com.aerospike.client.BatchResults; import com.aerospike.client.BatchWrite; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -56,7 +56,7 @@ public BatchOperate(Console console) { } @Override - public void runExample(AerospikeClient client, Parameters params) { + public void runExample(IAerospikeClient client, Parameters params) { writeRecords(client, params); batchReadOperate(client, params); batchReadOperateComplex(client, params); @@ -66,7 +66,7 @@ public void runExample(AerospikeClient client, Parameters params) { } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params ) { for (int i = 1; i <= RecordCount; i++) { @@ -91,7 +91,7 @@ private void writeRecords( /** * Perform read operation expressions in one batch. */ - private void batchReadOperate(AerospikeClient client, Parameters params) { + private void batchReadOperate(IAerospikeClient client, Parameters params) { console.info("batchReadOperate"); Key[] keys = new Key[RecordCount]; for (int i = 0; i < RecordCount; i++) { @@ -112,7 +112,7 @@ private void batchReadOperate(AerospikeClient client, Parameters params) { /** * Read results using varying read operations in one batch. */ - private void batchReadOperateComplex(AerospikeClient client, Parameters params) { + private void batchReadOperateComplex(IAerospikeClient client, Parameters params) { console.info("batchReadOperateComplex"); Expression exp1 = Exp.build(Exp.mul(Exp.intBin(BinName1), Exp.intBin(BinName2))); Expression exp2 = Exp.build(Exp.add(Exp.intBin(BinName1), Exp.intBin(BinName2))); @@ -157,7 +157,7 @@ private void batchReadOperateComplex(AerospikeClient client, Parameters params) /** * Perform list read operations in one batch. */ - private void batchListReadOperate(AerospikeClient client, Parameters params) { + private void batchListReadOperate(IAerospikeClient client, Parameters params) { console.info("batchListReadOperate"); Key[] keys = new Key[RecordCount]; for (int i = 0; i < RecordCount; i++) { @@ -194,7 +194,7 @@ private void batchListReadOperate(AerospikeClient client, Parameters params) { /** * Perform list read/write operations in one batch. */ - private void batchListWriteOperate(AerospikeClient client, Parameters params) { + private void batchListWriteOperate(IAerospikeClient client, Parameters params) { console.info("batchListWriteOperate"); Key[] keys = new Key[RecordCount]; for (int i = 0; i < RecordCount; i++) { @@ -228,7 +228,7 @@ private void batchListWriteOperate(AerospikeClient client, Parameters params) { /** * Read/Write records using varying operations in one batch. */ - private void batchWriteOperateComplex(AerospikeClient client, Parameters params) { + private void batchWriteOperateComplex(IAerospikeClient client, Parameters params) { console.info("batchWriteOperateComplex"); Expression wexp1 = Exp.build(Exp.add(Exp.intBin(BinName1), Exp.intBin(BinName2), Exp.val(1000))); Expression rexp1 = Exp.build(Exp.mul(Exp.intBin(BinName1), Exp.intBin(BinName2))); diff --git a/examples/src/com/aerospike/examples/DeleteBin.java b/examples/src/com/aerospike/examples/DeleteBin.java index 53f96713f..08df501b2 100644 --- a/examples/src/com/aerospike/examples/DeleteBin.java +++ b/examples/src/com/aerospike/examples/DeleteBin.java @@ -18,8 +18,8 @@ import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; @@ -33,7 +33,7 @@ public DeleteBin(Console console) { * Drop a bin from a record. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { console.info("Write multi-bin record."); Key key = new Key(params.namespace, params.set, "delbinkey"); String binName1 = "bin1"; diff --git a/examples/src/com/aerospike/examples/Example.java b/examples/src/com/aerospike/examples/Example.java index 9cdb8aaf8..8a93e6fe4 100644 --- a/examples/src/com/aerospike/examples/Example.java +++ b/examples/src/com/aerospike/examples/Example.java @@ -19,9 +19,10 @@ import java.lang.reflect.Constructor; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.proxy.AerospikeClientFactory; public abstract class Example { @@ -42,7 +43,7 @@ public static void runExamples(Console console, Parameters params, List Host[] hosts = Host.parseHosts(params.host, params.port); - AerospikeClient client = new AerospikeClient(policy, hosts); + IAerospikeClient client = AerospikeClientFactory.getClient(policy, params.useProxyClient, hosts); try { //params.setServerSpecific(client); @@ -59,7 +60,7 @@ public static void runExamples(Console console, Parameters params, List /** * Run client example. */ - public static void runExample(String exampleName, AerospikeClient client, Parameters params, Console console) throws Exception { + public static void runExample(String exampleName, IAerospikeClient client, Parameters params, Console console) throws Exception { String fullName = "com.aerospike.examples." + exampleName; Class cls = Class.forName(fullName); @@ -79,11 +80,11 @@ public Example(Console console) { this.console = console; } - public void run(AerospikeClient client, Parameters params) throws Exception { + public void run(IAerospikeClient client, Parameters params) throws Exception { console.info(this.getClass().getSimpleName() + " Begin"); runExample(client, params); console.info(this.getClass().getSimpleName() + " End"); } - public abstract void runExample(AerospikeClient client, Parameters params) throws Exception; + public abstract void runExample(IAerospikeClient client, Parameters params) throws Exception; } diff --git a/examples/src/com/aerospike/examples/Expire.java b/examples/src/com/aerospike/examples/Expire.java index 75b855a6b..4390b6ef4 100644 --- a/examples/src/com/aerospike/examples/Expire.java +++ b/examples/src/com/aerospike/examples/Expire.java @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.policy.WritePolicy; @@ -32,14 +32,14 @@ public Expire(Console console) { * Demonstrate various record expiration settings. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { expireExample(client, params); } /** * Write and twice read an expiration record. */ - private void expireExample(AerospikeClient client, Parameters params) throws Exception { + private void expireExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "expirekey "); Bin bin = new Bin("expirebin", "expirevalue"); diff --git a/examples/src/com/aerospike/examples/Generation.java b/examples/src/com/aerospike/examples/Generation.java index 5d7c7eeae..a1be3d930 100644 --- a/examples/src/com/aerospike/examples/Generation.java +++ b/examples/src/com/aerospike/examples/Generation.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -35,7 +35,7 @@ public Generation(Console console) { * Exercise record generation functionality. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "genkey"); String binName = "genbin"; diff --git a/examples/src/com/aerospike/examples/ListMap.java b/examples/src/com/aerospike/examples/ListMap.java index 515466a54..c6d495db9 100644 --- a/examples/src/com/aerospike/examples/ListMap.java +++ b/examples/src/com/aerospike/examples/ListMap.java @@ -22,8 +22,8 @@ import java.util.List; import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; @@ -37,7 +37,7 @@ public ListMap(Console console) { * Write List and Map objects directly instead of relying on java serializer. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { testListStrings(client, params); testListComplex(client, params); testMapStrings(client, params); @@ -48,7 +48,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti /** * Write/Read ArrayList directly instead of relying on java serializer. */ - private void testListStrings(AerospikeClient client, Parameters params) throws Exception { + private void testListStrings(IAerospikeClient client, Parameters params) throws Exception { console.info("Read/Write ArrayList"); Key key = new Key(params.namespace, params.set, "listkey1"); client.delete(params.writePolicy, key); @@ -75,7 +75,7 @@ private void testListStrings(AerospikeClient client, Parameters params) throws E /** * Write/Read ArrayList directly instead of relying on java serializer. */ - private void testListComplex(AerospikeClient client, Parameters params) throws Exception { + private void testListComplex(IAerospikeClient client, Parameters params) throws Exception { console.info("Read/Write ArrayList"); Key key = new Key(params.namespace, params.set, "listkey2"); client.delete(params.writePolicy, key); @@ -104,7 +104,7 @@ private void testListComplex(AerospikeClient client, Parameters params) throws E /** * Write/Read HashMap directly instead of relying on java serializer. */ - private void testMapStrings(AerospikeClient client, Parameters params) throws Exception { + private void testMapStrings(IAerospikeClient client, Parameters params) throws Exception { console.info("Read/Write HashMap"); Key key = new Key(params.namespace, params.set, "mapkey1"); client.delete(params.writePolicy, key); @@ -131,7 +131,7 @@ private void testMapStrings(AerospikeClient client, Parameters params) throws Ex /** * Write/Read HashMap directly instead of relying on java serializer. */ - private void testMapComplex(AerospikeClient client, Parameters params) throws Exception { + private void testMapComplex(IAerospikeClient client, Parameters params) throws Exception { console.info("Read/Write HashMap"); Key key = new Key(params.namespace, params.set, "mapkey2"); client.delete(params.writePolicy, key); @@ -179,7 +179,7 @@ private void testMapComplex(AerospikeClient client, Parameters params) throws Ex /** * Write/Read List/HashMap combination directly instead of relying on java serializer. */ - private void testListMapCombined(AerospikeClient client, Parameters params) throws Exception { + private void testListMapCombined(IAerospikeClient client, Parameters params) throws Exception { console.info("Read/Write List/HashMap"); Key key = new Key(params.namespace, params.set, "listmapkey"); client.delete(params.writePolicy, key); diff --git a/examples/src/com/aerospike/examples/Main.java b/examples/src/com/aerospike/examples/Main.java index 9df2202d9..fd2c4d739 100644 --- a/examples/src/com/aerospike/examples/Main.java +++ b/examples/src/com/aerospike/examples/Main.java @@ -134,6 +134,7 @@ public static void main(String[] args) { "Value: DIRECT_NIO | NETTY_NIO | NETTY_EPOLL | NETTY_KQUEUE | NETTY_IOURING" ); + options.addOption("proxy", false, "Use proxy client."); options.addOption("g", "gui", false, "Invoke GUI to selectively run tests."); options.addOption("d", "debug", false, "Run in debug mode."); options.addOption("u", "usage", false, "Print usage."); @@ -173,6 +174,10 @@ public static void main(String[] args) { params.eventLoopType = EventLoopType.valueOf(cl.getOptionValue("eventLoopType", "").toUpperCase()); } + if (cl.hasOption("proxy")) { + params.useProxyClient = true; + } + if (cl.hasOption("d")) { Log.setLevel(Level.DEBUG); } @@ -182,6 +187,14 @@ public static void main(String[] args) { } else { Console console = new Console(); + + // If the Aerospike server's default port (3000) is used and the proxy client is used, + // Reset the port to the proxy server's default port (4000). + if (params.port == 3000 && params.useProxyClient) { + console.info("Change proxy server port to 4000"); + params.port = 4000; + } + runExamples(console, params, exampleNames); } } diff --git a/examples/src/com/aerospike/examples/Operate.java b/examples/src/com/aerospike/examples/Operate.java index c9c238b05..10b54ed50 100644 --- a/examples/src/com/aerospike/examples/Operate.java +++ b/examples/src/com/aerospike/examples/Operate.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -33,7 +33,7 @@ public Operate(Console console) { * Demonstrate multiple operations on a single record in one call. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { // Write initial record. Key key = new Key(params.namespace, params.set, "opkey"); Bin bin1 = new Bin("bin1", 7); @@ -50,7 +50,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti touchReadMultipleBins(client, params.writePolicy, key, bin1.name, bin2.name, bin3.name); } - private void addWriteGet(AerospikeClient client, WritePolicy policy, Key key, String bin1, String bin2) + private void addWriteGet(IAerospikeClient client, WritePolicy policy, Key key, String bin1, String bin2) throws Exception { // Add integer, write new string and read record. Bin bin11 = new Bin(bin1, 4); @@ -73,7 +73,7 @@ private void addWriteGet(AerospikeClient client, WritePolicy policy, Key key, St } private void touchReadMultipleBins( - AerospikeClient client, + IAerospikeClient client, WritePolicy policy, Key key, String bin1, diff --git a/examples/src/com/aerospike/examples/OperateBit.java b/examples/src/com/aerospike/examples/OperateBit.java index dcd6b2550..0e8b2c1f0 100644 --- a/examples/src/com/aerospike/examples/OperateBit.java +++ b/examples/src/com/aerospike/examples/OperateBit.java @@ -18,8 +18,8 @@ import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -36,14 +36,14 @@ public OperateBit(Console console) { * Perform operations on a blob bin. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { runSimpleExample(client, params); } /** * Simple example of bit functionality. */ - public void runSimpleExample(AerospikeClient client, Parameters params) throws Exception { + public void runSimpleExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "bitkey"); String binName = "bitbin"; diff --git a/examples/src/com/aerospike/examples/OperateList.java b/examples/src/com/aerospike/examples/OperateList.java index f021d2adc..e492c7d44 100644 --- a/examples/src/com/aerospike/examples/OperateList.java +++ b/examples/src/com/aerospike/examples/OperateList.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -38,7 +38,7 @@ public OperateList(Console console) { * Perform operations on a list bin. */ @Override - public void runExample(AerospikeClient client, Parameters params) { + public void runExample(IAerospikeClient client, Parameters params) { runSimpleExample(client, params); runNestedExample(client, params); } @@ -46,7 +46,7 @@ public void runExample(AerospikeClient client, Parameters params) { /** * Simple example of list functionality. */ - public void runSimpleExample(AerospikeClient client, Parameters params) { + public void runSimpleExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "listkey"); String binName = "listbin"; @@ -85,7 +85,7 @@ record = client.operate(params.writePolicy, key, /** * Operate on a list of lists. */ - public void runNestedExample(AerospikeClient client, Parameters params) { + public void runNestedExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "listkey2"); String binName = "listbin"; diff --git a/examples/src/com/aerospike/examples/OperateMap.java b/examples/src/com/aerospike/examples/OperateMap.java index 720854ec9..bf50467f2 100644 --- a/examples/src/com/aerospike/examples/OperateMap.java +++ b/examples/src/com/aerospike/examples/OperateMap.java @@ -22,8 +22,8 @@ import java.util.List; import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Operation; import com.aerospike.client.Record; @@ -46,7 +46,7 @@ public OperateMap(Console console) { * Perform operations on a map bin. */ @Override - public void runExample(AerospikeClient client, Parameters params) { + public void runExample(IAerospikeClient client, Parameters params) { runSimpleExample(client, params); runScoreExample(client, params); runListRangeExample(client, params); @@ -58,7 +58,7 @@ public void runExample(AerospikeClient client, Parameters params) { /** * Simple example of map operate functionality. */ - public void runSimpleExample(AerospikeClient client, Parameters params) { + public void runSimpleExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey"); String binName = "mapbin"; @@ -97,7 +97,7 @@ record = client.operate(params.writePolicy, key, /** * Map score example. */ - public void runScoreExample(AerospikeClient client, Parameters params) { + public void runScoreExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey"); String binName = "mapbin"; @@ -143,7 +143,7 @@ record = client.operate(params.writePolicy, key, /** * Value list range example. */ - public void runListRangeExample(AerospikeClient client, Parameters params) { + public void runListRangeExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey"); String binName = "mapbin"; @@ -199,7 +199,7 @@ record = client.operate(params.writePolicy, key, /** * Operate on a map of maps. */ - public void runNestedExample(AerospikeClient client, Parameters params) { + public void runNestedExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey2"); String binName = "mapbin"; @@ -232,7 +232,7 @@ record = client.get(params.policy, key); console.info("Record: " + record); } - public void runNestedMapCreateExample(AerospikeClient client, Parameters params) { + public void runNestedMapCreateExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey2"); String binName = "mapbin"; @@ -267,7 +267,7 @@ record = client.get(params.policy, key); console.info("Record: " + record); } - public void runNestedListCreateExample(AerospikeClient client, Parameters params) { + public void runNestedListCreateExample(IAerospikeClient client, Parameters params) { Key key = new Key(params.namespace, params.set, "mapkey3"); String binName = "mapbin"; diff --git a/examples/src/com/aerospike/examples/Parameters.java b/examples/src/com/aerospike/examples/Parameters.java index e70e1b92c..210d44bfb 100644 --- a/examples/src/com/aerospike/examples/Parameters.java +++ b/examples/src/com/aerospike/examples/Parameters.java @@ -39,6 +39,7 @@ public class Parameters { EventLoopType eventLoopType = EventLoopType.DIRECT_NIO; int maxCommandsInProcess; int maxCommandsInQueue; + boolean useProxyClient; protected Parameters(TlsPolicy policy, String host, int port, String user, String password, AuthMode authMode, String namespace, String set) { this.host = host; diff --git a/examples/src/com/aerospike/examples/Prepend.java b/examples/src/com/aerospike/examples/Prepend.java index c07857d78..f538655d9 100644 --- a/examples/src/com/aerospike/examples/Prepend.java +++ b/examples/src/com/aerospike/examples/Prepend.java @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; @@ -31,7 +31,7 @@ public Prepend(Console console) { * Prepend string to an existing string. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "prependkey"); String binName = "prependbin"; diff --git a/examples/src/com/aerospike/examples/PutGet.java b/examples/src/com/aerospike/examples/PutGet.java index 0e78e129b..576ef1ea6 100644 --- a/examples/src/com/aerospike/examples/PutGet.java +++ b/examples/src/com/aerospike/examples/PutGet.java @@ -16,8 +16,8 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; @@ -31,7 +31,7 @@ public PutGet(Console console) { * Write and read a bin value. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { runMultiBinTest(client, params); runGetHeaderTest(client, params); } @@ -39,7 +39,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti /** * Execute put and get on a server configured as multi-bin. This is the server default. */ - private void runMultiBinTest(AerospikeClient client, Parameters params) throws Exception { + private void runMultiBinTest(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "putgetkey"); Bin bin1 = new Bin("bin1", "value1"); Bin bin2 = new Bin("bin2", "value2"); @@ -78,7 +78,7 @@ private void validateBin(Key key, Bin bin, Record record) { /** * Read record header data. */ - private void runGetHeaderTest(AerospikeClient client, Parameters params) throws Exception { + private void runGetHeaderTest(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "putgetkey"); console.info("Get record header: namespace=%s set=%s key=%s", key.namespace, key.setName, key.userKey); diff --git a/examples/src/com/aerospike/examples/QueryAverage.java b/examples/src/com/aerospike/examples/QueryAverage.java index f18aef581..940b72f50 100644 --- a/examples/src/com/aerospike/examples/QueryAverage.java +++ b/examples/src/com/aerospike/examples/QueryAverage.java @@ -18,9 +18,9 @@ import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.ResultCode; @@ -42,26 +42,33 @@ public QueryAverage(Console console) { * Create secondary index and query on it and apply aggregation user defined function. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "avgindex"; String keyPrefix = "avgkey"; String binName = "l2"; int size = 10; - register(client, params); - createIndex(client, params, indexName, binName); + // Proxy client does not support register() nor createIndex(), so must assume + // there are already created to run this test. + if (! params.useProxyClient) { + register(client, params); + createIndex(client, params, indexName, binName); + } writeRecords(client, params, keyPrefix, size); runQuery(client, params, indexName, binName); - client.dropIndex(params.policy, params.namespace, params.set, indexName); + + // Do not drop index because after native client tests run, the proxy + // client tests need the index to exist. + //client.dropIndex(params.policy, params.namespace, params.set, indexName); } - private void register(AerospikeClient client, Parameters params) throws Exception { + private void register(IAerospikeClient client, Parameters params) throws Exception { RegisterTask task = client.register(params.policy, "udf/average_example.lua", "average_example.lua", Language.LUA); task.waitTillComplete(); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -84,7 +91,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, int size @@ -101,7 +108,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/QueryCollection.java b/examples/src/com/aerospike/examples/QueryCollection.java index 974a15003..b09f17c78 100644 --- a/examples/src/com/aerospike/examples/QueryCollection.java +++ b/examples/src/com/aerospike/examples/QueryCollection.java @@ -19,9 +19,9 @@ import java.util.HashMap; import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -43,7 +43,7 @@ public QueryCollection(Console console) { * Query records using a map index. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "mapkey_index"; String keyPrefix = "qkey"; String mapKeyPrefix = "mkey"; @@ -61,7 +61,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -84,7 +84,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -115,7 +115,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName, diff --git a/examples/src/com/aerospike/examples/QueryExecute.java b/examples/src/com/aerospike/examples/QueryExecute.java index 8896c11fc..6fb3660e2 100644 --- a/examples/src/com/aerospike/examples/QueryExecute.java +++ b/examples/src/com/aerospike/examples/QueryExecute.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.Record; @@ -43,7 +43,7 @@ public QueryExecute(Console console) { * Apply user defined function on records that match the query filter. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "qeindex1"; String keyPrefix = "qekey"; String binName1 = "qebin1"; @@ -58,13 +58,13 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti client.dropIndex(params.policy, params.namespace, params.set, indexName); } - private void register(AerospikeClient client, Parameters params) throws Exception { + private void register(IAerospikeClient client, Parameters params) throws Exception { RegisterTask task = client.register(params.policy, "udf/record_example.lua", "record_example.lua", Language.LUA); task.waitTillComplete(); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -87,7 +87,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName1, @@ -103,7 +103,7 @@ private void writeRecords( } private void runQueryExecute( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName1, @@ -128,7 +128,7 @@ private void runQueryExecute( } private void validateRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName1, diff --git a/examples/src/com/aerospike/examples/QueryExp.java b/examples/src/com/aerospike/examples/QueryExp.java index 6ecc96193..c0a9d3a54 100644 --- a/examples/src/com/aerospike/examples/QueryExp.java +++ b/examples/src/com/aerospike/examples/QueryExp.java @@ -19,9 +19,9 @@ import java.util.Calendar; import java.util.GregorianCalendar; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -45,21 +45,25 @@ public QueryExp(Console console) { * Perform secondary index query with a predicate filter. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "predidx"; String binName = "idxbin"; int size = 50; - createIndex(client, params, indexName, binName); + if (!params.useProxyClient) { + createIndex(client, params, indexName, binName); + } + writeRecords(client, params, binName, size); runQuery1(client, params, binName); runQuery2(client, params, binName); runQuery3(client, params, binName); - client.dropIndex(params.policy, params.namespace, params.set, indexName); + + //client.dropIndex(params.policy, params.namespace, params.set, indexName); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -82,7 +86,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String binName, int size @@ -109,7 +113,7 @@ else if (i % 2 == 0) { } private void runQuery1( - AerospikeClient client, + IAerospikeClient client, Parameters params, String binName ) throws Exception { @@ -128,7 +132,7 @@ private void runQuery1( // Predicates are applied on query results on server side. // Predicates can reference any bin. - QueryPolicy policy = new QueryPolicy(client.queryPolicyDefault); + QueryPolicy policy = new QueryPolicy(client.getQueryPolicyDefault()); policy.filterExp = Exp.build( Exp.or( Exp.and( @@ -150,7 +154,7 @@ private void runQuery1( } private void runQuery2( - AerospikeClient client, + IAerospikeClient client, Parameters params, String binName ) throws Exception { @@ -167,7 +171,7 @@ private void runQuery2( stmt.setSetName(params.set); stmt.setFilter(Filter.range(binName, begin, end)); - QueryPolicy policy = new QueryPolicy(client.queryPolicyDefault); + QueryPolicy policy = new QueryPolicy(client.getQueryPolicyDefault()); policy.filterExp = Exp.build( Exp.and( Exp.ge(Exp.lastUpdate(), Exp.val(beginTime)), @@ -187,7 +191,7 @@ private void runQuery2( } private void runQuery3( - AerospikeClient client, + IAerospikeClient client, Parameters params, String binName ) throws Exception { @@ -202,7 +206,7 @@ private void runQuery3( stmt.setSetName(params.set); stmt.setFilter(Filter.range(binName, begin, end)); - QueryPolicy policy = new QueryPolicy(client.queryPolicyDefault); + QueryPolicy policy = new QueryPolicy(client.getQueryPolicyDefault()); policy.filterExp = Exp.build( Exp.regexCompare("prefix.*suffix", RegexFlag.ICASE | RegexFlag.NEWLINE, Exp.stringBin("bin3"))); diff --git a/examples/src/com/aerospike/examples/QueryFilter.java b/examples/src/com/aerospike/examples/QueryFilter.java index 124d17c01..88b950102 100644 --- a/examples/src/com/aerospike/examples/QueryFilter.java +++ b/examples/src/com/aerospike/examples/QueryFilter.java @@ -18,9 +18,9 @@ import java.util.Map; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.ResultCode; @@ -44,7 +44,7 @@ public QueryFilter(Console console) { * user defined function. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "profileindex"; String keyPrefix = "profilekey"; String binName = "name"; @@ -56,13 +56,13 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti client.dropIndex(params.policy, params.namespace, params.set, indexName); } - private void register(AerospikeClient client, Parameters params) throws Exception { + private void register(IAerospikeClient client, Parameters params) throws Exception { RegisterTask task = client.register(params.policy, "udf/filter_example.lua", "filter_example.lua", Language.LUA); task.waitTillComplete(); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -85,7 +85,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName @@ -96,7 +96,7 @@ private void writeRecords( } private void writeRecord( - AerospikeClient client, + IAerospikeClient client, Parameters params, String userKey, String name, @@ -113,7 +113,7 @@ private void writeRecord( @SuppressWarnings("unchecked") private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/QueryGeoCollection.java b/examples/src/com/aerospike/examples/QueryGeoCollection.java index 039d3102f..637d430e8 100644 --- a/examples/src/com/aerospike/examples/QueryGeoCollection.java +++ b/examples/src/com/aerospike/examples/QueryGeoCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -22,9 +22,9 @@ import java.util.List; import java.util.Set; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -47,14 +47,14 @@ public QueryGeoCollection(Console console) { * Perform region queries using a Geo index on a collection. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { runMapExample(client,params); runMapKeyExample(client,params); runListExample(client,params); } - private void runMapExample(AerospikeClient client, Parameters params) throws Exception { + private void runMapExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "geo_map"; String keyPrefix = "map"; String mapValuePrefix = "mv"; @@ -70,7 +70,7 @@ private void runMapExample(AerospikeClient client, Parameters params) throws Exc deleteRecords(client,params, keyPrefix, size); } - private void runMapKeyExample(AerospikeClient client, Parameters params) throws Exception { + private void runMapKeyExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "geo_mapkey"; String keyPrefix = "mapkey"; String mapValuePrefix = "mk"; @@ -86,7 +86,7 @@ private void runMapKeyExample(AerospikeClient client, Parameters params) throws deleteRecords(client,params, keyPrefix, size); } - private void runListExample(AerospikeClient client, Parameters params) throws Exception { + private void runListExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "geo_list"; String keyPrefix = "list"; String binName = "geo_list_bin"; @@ -102,7 +102,7 @@ private void runListExample(AerospikeClient client, Parameters params) throws Ex } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, IndexCollectionType indexType, String indexName, @@ -127,7 +127,7 @@ private void createIndex( } private void writeMapRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -164,7 +164,7 @@ private void writeMapRecords( } private void writeMapKeyRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -201,7 +201,7 @@ private void writeMapKeyRecords( } private void writeListRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -238,7 +238,7 @@ private void writeListRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String binName, String binName2, @@ -281,7 +281,7 @@ private void runQuery( } private void deleteRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, int size diff --git a/examples/src/com/aerospike/examples/QueryInteger.java b/examples/src/com/aerospike/examples/QueryInteger.java index cc3808286..71f5af6aa 100644 --- a/examples/src/com/aerospike/examples/QueryInteger.java +++ b/examples/src/com/aerospike/examples/QueryInteger.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,7 +40,7 @@ public QueryInteger(Console console) { * Create secondary index on an integer bin and query on it. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "queryindexint"; String keyPrefix = "querykeyint"; String binName = "querybinint"; @@ -53,7 +53,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -76,7 +76,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -92,7 +92,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/QueryPage.java b/examples/src/com/aerospike/examples/QueryPage.java index 577558eb3..9bef80c4d 100644 --- a/examples/src/com/aerospike/examples/QueryPage.java +++ b/examples/src/com/aerospike/examples/QueryPage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.ResultCode; import com.aerospike.client.policy.Policy; @@ -39,7 +39,7 @@ public QueryPage(Console console) { * Query in pages. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "pqidx"; String binName = "bin"; String setName = "pq"; @@ -79,7 +79,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String indexName, @@ -102,7 +102,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String binName, diff --git a/examples/src/com/aerospike/examples/QueryRegion.java b/examples/src/com/aerospike/examples/QueryRegion.java index 4005e2ce2..ddfbc7847 100644 --- a/examples/src/com/aerospike/examples/QueryRegion.java +++ b/examples/src/com/aerospike/examples/QueryRegion.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,7 +40,7 @@ public QueryRegion(Console console) { * Perform region/radius queries using a Geo index. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "queryindexloc"; String keyPrefix = "querykeyloc"; String binName = "querybinloc"; @@ -54,7 +54,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -79,7 +79,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -103,7 +103,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -155,7 +155,7 @@ private void runQuery( } private void runRadiusQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/QueryRegionFilter.java b/examples/src/com/aerospike/examples/QueryRegionFilter.java index 1239a98a7..4a5b22edc 100644 --- a/examples/src/com/aerospike/examples/QueryRegionFilter.java +++ b/examples/src/com/aerospike/examples/QueryRegionFilter.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.ResultCode; @@ -41,7 +41,7 @@ public QueryRegionFilter(Console console) { * Perform region query using a Geo index with an aggregation filter. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "filterindexloc"; String keyPrefix = "filterkeyloc"; String binName1 = "filterloc"; @@ -55,14 +55,14 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti client.dropIndex(params.policy, params.namespace, params.set, indexName); } - private void register(AerospikeClient client, Parameters params) throws Exception { + private void register(IAerospikeClient client, Parameters params) throws Exception { RegisterTask task = client.register(params.policy, "udf/geo_filter_example.lua", "geo_filter_example.lua", Language.LUA); task.waitTillComplete(); } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -87,7 +87,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName1, @@ -122,7 +122,7 @@ else if (i % 2 == 0) { } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName1, diff --git a/examples/src/com/aerospike/examples/QueryResume.java b/examples/src/com/aerospike/examples/QueryResume.java index dc2a10ac2..efc281d2f 100644 --- a/examples/src/com/aerospike/examples/QueryResume.java +++ b/examples/src/com/aerospike/examples/QueryResume.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -18,9 +18,9 @@ import java.util.concurrent.atomic.AtomicInteger; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -42,12 +42,14 @@ public QueryResume(Console console) { * Terminate a query and then resume query later. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "qridx"; String binName = "bin"; String setName = "qr"; - createIndex(client, params, setName, indexName, binName); + if (!params.useProxyClient) { + createIndex(client, params, setName, indexName, binName); + } writeRecords(client, params, setName, binName, 200); Statement stmt = new Statement(); @@ -98,7 +100,7 @@ public void onRecord(Key key, Record record) { } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String indexName, @@ -121,7 +123,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String binName, diff --git a/examples/src/com/aerospike/examples/QueryString.java b/examples/src/com/aerospike/examples/QueryString.java index 8d6dd4f9e..96e1d3a2c 100644 --- a/examples/src/com/aerospike/examples/QueryString.java +++ b/examples/src/com/aerospike/examples/QueryString.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,7 +40,7 @@ public QueryString(Console console) { * Create secondary index on a string bin and query on it. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "queryindex"; String keyPrefix = "querykey"; String valuePrefix = "queryvalue"; @@ -54,7 +54,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -77,7 +77,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -96,7 +96,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName, diff --git a/examples/src/com/aerospike/examples/QuerySum.java b/examples/src/com/aerospike/examples/QuerySum.java index 5c4d60a33..22a414940 100644 --- a/examples/src/com/aerospike/examples/QuerySum.java +++ b/examples/src/com/aerospike/examples/QuerySum.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.ResultCode; @@ -41,7 +41,7 @@ public QuerySum(Console console) { * Query records and calculate sum using a user-defined aggregation function. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "aggindex"; String keyPrefix = "aggkey"; String binName = "aggbin"; @@ -54,7 +54,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti client.dropIndex(params.policy, params.namespace, params.set, indexName); } - private void register(AerospikeClient client, Parameters params) throws Exception { + private void register(IAerospikeClient client, Parameters params) throws Exception { RegisterTask task = client.register(params.policy, "udf/sum_example.lua", "sum_example.lua", Language.LUA); // Alternately register from resource. // RegisterTask task = client.register(params.policy, QuerySum.class.getClassLoader(), "udf/sum_example.lua", "sum_example.lua", Language.LUA); @@ -62,7 +62,7 @@ private void register(AerospikeClient client, Parameters params) throws Exceptio } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -85,7 +85,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -103,7 +103,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/Replace.java b/examples/src/com/aerospike/examples/Replace.java index 901415466..8cff7a2b9 100644 --- a/examples/src/com/aerospike/examples/Replace.java +++ b/examples/src/com/aerospike/examples/Replace.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,12 +40,12 @@ public Replace(Console console) { * the server does not have to read the existing record before overwriting it. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { runReplaceExample(client, params); runReplaceOnlyExample(client, params); } - public void runReplaceExample(AerospikeClient client, Parameters params) throws Exception { + public void runReplaceExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "replacekey"); Bin bin1 = new Bin("bin1", "value1"); Bin bin2 = new Bin("bin2", "value2"); @@ -88,7 +88,7 @@ public void runReplaceExample(AerospikeClient client, Parameters params) throws validateBin(key, bin3, record); } - public void runReplaceOnlyExample(AerospikeClient client, Parameters params) throws Exception { + public void runReplaceOnlyExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "replaceonlykey"); Bin bin = new Bin("bin", "value"); diff --git a/examples/src/com/aerospike/examples/ScanPage.java b/examples/src/com/aerospike/examples/ScanPage.java index 67bb6aeda..75a1677cf 100644 --- a/examples/src/com/aerospike/examples/ScanPage.java +++ b/examples/src/com/aerospike/examples/ScanPage.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -18,8 +18,8 @@ import java.util.concurrent.atomic.AtomicInteger; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ScanCallback; @@ -38,7 +38,7 @@ public ScanPage(Console console) { * Scan in pages. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String binName = "bin"; String setName = "page"; @@ -62,7 +62,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String binName, diff --git a/examples/src/com/aerospike/examples/ScanParallel.java b/examples/src/com/aerospike/examples/ScanParallel.java index 2b51a2be3..a38fd7143 100644 --- a/examples/src/com/aerospike/examples/ScanParallel.java +++ b/examples/src/com/aerospike/examples/ScanParallel.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger; -import com.aerospike.client.AerospikeClient; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ScanCallback; @@ -36,7 +36,7 @@ public ScanParallel(Console console) { * Scan all nodes in parallel and read all records in a set. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { console.info("Scan parallel: namespace=" + params.namespace + " set=" + params.set); recordCount = new AtomicInteger(); long begin = System.currentTimeMillis(); diff --git a/examples/src/com/aerospike/examples/ScanResume.java b/examples/src/com/aerospike/examples/ScanResume.java index 6f64f2aca..6810666f0 100644 --- a/examples/src/com/aerospike/examples/ScanResume.java +++ b/examples/src/com/aerospike/examples/ScanResume.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ScanCallback; @@ -38,7 +38,7 @@ public ScanResume(Console console) { * Terminate a scan and then resume scan later. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String binName = "bin"; String setName = "resume"; @@ -73,7 +73,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String setName, String binName, diff --git a/examples/src/com/aerospike/examples/ScanSeries.java b/examples/src/com/aerospike/examples/ScanSeries.java index f3d8a58d5..6d5c9c214 100644 --- a/examples/src/com/aerospike/examples/ScanSeries.java +++ b/examples/src/com/aerospike/examples/ScanSeries.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import com.aerospike.client.AerospikeClient; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ScanCallback; @@ -38,7 +38,7 @@ public ScanSeries(Console console) { * Scan all nodes in series and read all records in all sets. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { console.info("Scan series: namespace=" + params.namespace + " set=" + params.set); // Limit scan to recordsPerSecond. This will take more time, but it will reduce diff --git a/examples/src/com/aerospike/examples/ServerInfo.java b/examples/src/com/aerospike/examples/ServerInfo.java index 8069149dc..b6a667742 100644 --- a/examples/src/com/aerospike/examples/ServerInfo.java +++ b/examples/src/com/aerospike/examples/ServerInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -18,7 +18,7 @@ import java.util.Map; -import com.aerospike.client.AerospikeClient; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Info; import com.aerospike.client.cluster.Node; @@ -32,7 +32,7 @@ public ServerInfo(Console console) { * Query server configuration, cluster status and namespace configuration. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Node node = client.getNodes()[0]; GetServerConfig(node, params); console.write(""); diff --git a/examples/src/com/aerospike/examples/StoreKey.java b/examples/src/com/aerospike/examples/StoreKey.java index c0a14dcbe..39664e2d8 100644 --- a/examples/src/com/aerospike/examples/StoreKey.java +++ b/examples/src/com/aerospike/examples/StoreKey.java @@ -16,9 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.ResultCode; @@ -40,7 +40,7 @@ public StoreKey(Console console) { * Store user key on server using WritePolicy.sendKey option. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { String indexName = "skindex"; String keyPrefix = "skkey"; String binName = "skbin"; @@ -53,7 +53,7 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti } private void createIndex( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName @@ -76,7 +76,7 @@ private void createIndex( } private void writeRecords( - AerospikeClient client, + IAerospikeClient client, Parameters params, String keyPrefix, String binName, @@ -94,7 +94,7 @@ private void writeRecords( } private void runQuery( - AerospikeClient client, + IAerospikeClient client, Parameters params, String indexName, String binName diff --git a/examples/src/com/aerospike/examples/Touch.java b/examples/src/com/aerospike/examples/Touch.java index 018038b06..615a2e6ae 100644 --- a/examples/src/com/aerospike/examples/Touch.java +++ b/examples/src/com/aerospike/examples/Touch.java @@ -16,10 +16,9 @@ */ package com.aerospike.examples; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; -import com.aerospike.client.Operation; import com.aerospike.client.Record; import com.aerospike.client.policy.WritePolicy; @@ -33,7 +32,7 @@ public Touch(Console console) { * Demonstrate touch command. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { + public void runExample(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "touchkey"); Bin bin = new Bin("touchbin", "touchvalue"); @@ -44,24 +43,12 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti console.info("Touch same record with 5 second expiration."); writePolicy.expiration = 5; - Record record = client.operate(writePolicy, key, Operation.touch(), Operation.getHeader()); - - if (record == null) { - throw new Exception(String.format( - "Failed to get: namespace=%s set=%s key=%s bin=%s value=%s", - key.namespace, key.setName, key.userKey, bin.name, null)); - } - - if (record.expiration == 0) { - throw new Exception(String.format( - "Failed to get record expiration: namespace=%s set=%s key=%s", - key.namespace, key.setName, key.userKey)); - } + client.touch(writePolicy, key); console.info("Sleep 3 seconds."); Thread.sleep(3000); - record = client.get(params.policy, key, bin.name); + Record record = client.get(params.policy, key, bin.name); if (record == null) { throw new Exception(String.format( @@ -73,9 +60,9 @@ record = client.get(params.policy, key, bin.name); console.info("Sleep 4 seconds."); Thread.sleep(4000); - record = client.get(params.policy, key, bin.name); + boolean exists = client.exists(params.policy, key); - if (record == null) { + if (! exists) { console.info("Success. Record expired as expected."); } else { diff --git a/examples/src/com/aerospike/examples/UserDefinedFunction.java b/examples/src/com/aerospike/examples/UserDefinedFunction.java index 84fe9ca32..724608410 100644 --- a/examples/src/com/aerospike/examples/UserDefinedFunction.java +++ b/examples/src/com/aerospike/examples/UserDefinedFunction.java @@ -23,8 +23,8 @@ import java.util.HashMap; import java.util.List; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Language; import com.aerospike.client.Record; @@ -41,8 +41,13 @@ public UserDefinedFunction(Console console) { * Register user defined function and call it. */ @Override - public void runExample(AerospikeClient client, Parameters params) throws Exception { - register(client, params); + public void runExample(IAerospikeClient client, Parameters params) throws Exception { + // Register is not supported in the proxy client. To run this example with the proxy client, + // first run example with native client (which supports register) and then run proxy client. + if (! params.useProxyClient) { + register(client, params); + } + writeUsingUdf(client, params); writeIfGenerationNotChanged(client, params); writeIfNotExists(client, params); @@ -52,12 +57,14 @@ public void runExample(AerospikeClient client, Parameters params) throws Excepti writeBlobUsingUdf(client, params); } - private void register(AerospikeClient client, Parameters params) throws Exception { - RegisterTask task = client.register(params.policy, "udf/record_example.lua", "record_example.lua", Language.LUA); + private void register(IAerospikeClient client, Parameters params) throws Exception { + String filename = "record_example.lua"; + console.info("Register: " + filename); + RegisterTask task = client.register(params.policy, "udf/record_example.lua", filename, Language.LUA); task.waitTillComplete(); } - private void writeUsingUdf(AerospikeClient client, Parameters params) throws Exception { + private void writeUsingUdf(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey1"); Bin bin = new Bin("udfbin1", "string value"); @@ -76,7 +83,7 @@ private void writeUsingUdf(AerospikeClient client, Parameters params) throws Exc } } - private void writeIfGenerationNotChanged(AerospikeClient client, Parameters params) throws Exception { + private void writeIfGenerationNotChanged(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey2"); Bin bin = new Bin("udfbin2", "string value"); @@ -91,7 +98,7 @@ private void writeIfGenerationNotChanged(AerospikeClient client, Parameters para console.info("Record written."); } - private void writeIfNotExists(AerospikeClient client, Parameters params) throws Exception { + private void writeIfNotExists(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey3"); String binName = "udfbin3"; @@ -131,7 +138,7 @@ record = client.get(params.policy, key, binName); } } - private void writeWithValidation(AerospikeClient client, Parameters params) throws Exception { + private void writeWithValidation(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey4"); String binName = "udfbin4"; @@ -152,7 +159,7 @@ private void writeWithValidation(AerospikeClient client, Parameters params) thro } } - private void writeListMapUsingUdf(AerospikeClient client, Parameters params) throws Exception { + private void writeListMapUsingUdf(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey5"); ArrayList inner = new ArrayList(); @@ -187,7 +194,7 @@ private void writeListMapUsingUdf(AerospikeClient client, Parameters params) thr } } - private void appendListUsingUdf(AerospikeClient client, Parameters params) throws Exception { + private void appendListUsingUdf(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey5"); String binName = "udfbin5"; String value = "appended value"; @@ -221,7 +228,7 @@ private void appendListUsingUdf(AerospikeClient client, Parameters params) throw } } - private void writeBlobUsingUdf(AerospikeClient client, Parameters params) throws Exception { + private void writeBlobUsingUdf(IAerospikeClient client, Parameters params) throws Exception { Key key = new Key(params.namespace, params.set, "udfkey6"); String binName = "udfbin6"; diff --git a/pom.xml b/pom.xml index f9c3ce629..b4bf6c1bd 100644 --- a/pom.xml +++ b/pom.xml @@ -8,12 +8,12 @@ 6.1.11 pom https://github.com/aerospike/aerospike-client-java - + Aerospike Inc. https://www.aerospike.com - + The Apache License, Version 2.0 @@ -23,6 +23,7 @@ client + proxy examples benchmarks test @@ -39,6 +40,8 @@ 3.2.0 4.1.94.Final + 2.0.61.Final + 1.56.1 3.0.1 0.4 1.5.0 @@ -53,6 +56,12 @@ ${project.version} + + com.aerospike + aerospike-proxy-client + ${project.version} + + com.aerospike aerospike-query-engine @@ -98,6 +107,12 @@ ${netty.version} + + io.netty + netty-tcnative-boringssl-static + ${netty.tcnative.version} + + org.luaj luaj-jse @@ -122,6 +137,13 @@ ${junit.version} test + + + io.grpc + grpc-netty + ${grpc.version} + + @@ -150,4 +172,14 @@ + + diff --git a/proxy/README.md b/proxy/README.md new file mode 100644 index 000000000..12a812520 --- /dev/null +++ b/proxy/README.md @@ -0,0 +1,18 @@ +Aerospike Java Proxy Client Library +=================================== + +The proxy client is designed to communicate with a proxy server in dbaas +(database as a service) applications. The communication is performed via GRPC +and HTTP/2. The proxy server relays the database commands to the Aerospike +server. The proxy client does not have knowledge of Aerospike server nodes. +The proxy server communicates directly with Aerospike server nodes. + +The proxy client's AerospikeClientProxy implements the same IAerospikeClient +interface as the native client's AerospikeClient. AerospikeClientProxy supports +single record, batch and most scan/query commands. AerospikeClientProxy does +not support info and user admin commands nor scan/query commands that are +directed to a single node. + +The source code can be imported into your IDE and/or built using Maven. + + mvn install diff --git a/proxy/pom.xml b/proxy/pom.xml new file mode 100644 index 000000000..529354b0c --- /dev/null +++ b/proxy/pom.xml @@ -0,0 +1,107 @@ + + 4.0.0 + + + com.aerospike + aerospike-parent + 6.1.11 + + aerospike-proxy-client + jar + + aerospike-proxy-client + + + + com.aerospike + aerospike-client + + + + com.aerospike + aerospike-proxy-stub + 0.10.0 + + + + io.grpc + grpc-netty + + + + io.netty + netty-transport + + + + io.netty + netty-transport-native-epoll + linux-x86_64 + + + + io.netty + netty-tcnative-boringssl-static + + + + io.netty + netty-handler + + + + com.auth0 + java-jwt + 4.2.1 + + + + org.jctools + jctools-core + 4.0.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.1 + + + + + ${project.basedir}/src + + + resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + maven-assembly-plugin + + + jar-with-dependencies + + + + + make-my-jar-with-dependencies + package + + single + + + + + + + diff --git a/proxy/resources/project.properties b/proxy/resources/project.properties new file mode 100644 index 000000000..de55c14db --- /dev/null +++ b/proxy/resources/project.properties @@ -0,0 +1,2 @@ +name=${project.name} +version=${project.version} diff --git a/proxy/src/com/aerospike/client/proxy/AerospikeClientFactory.java b/proxy/src/com/aerospike/client/proxy/AerospikeClientFactory.java new file mode 100644 index 000000000..cbc7ff82d --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/AerospikeClientFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeClient; +import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.policy.ClientPolicy; + +/** + * Factory class AerospikeClientFactory will generate either a native client or a proxy client, + * based on whether isProxy is true or false. This allows an application to work with either + * Aerospike native servers or proxy servers used in the database-as-a-service offering (dbaas). + */ +public class AerospikeClientFactory { + /** + * Return either a native Aerospike client or a proxy client, based on isProxy. + * + * @param clientPolicy client configuration parameters, pass in null for defaults + * @param isProxy if true, return AerospikeClientProxy, otherwise return AerospikeClient + * @param hosts array of server hosts that the client can connect + * @return IAerospikeClient + */ + public static IAerospikeClient getClient(ClientPolicy clientPolicy, boolean isProxy, Host... hosts) { + if (isProxy) { + return new AerospikeClientProxy(clientPolicy, hosts); + } + else { + return new AerospikeClient(clientPolicy, hosts); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/AerospikeClientProxy.java b/proxy/src/com/aerospike/client/proxy/AerospikeClientProxy.java new file mode 100644 index 000000000..ba1208ea7 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/AerospikeClientProxy.java @@ -0,0 +1,2806 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.BatchDelete; +import com.aerospike.client.BatchRead; +import com.aerospike.client.BatchRecord; +import com.aerospike.client.BatchResults; +import com.aerospike.client.BatchUDF; +import com.aerospike.client.BatchWrite; +import com.aerospike.client.Bin; +import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; +import com.aerospike.client.Key; +import com.aerospike.client.Language; +import com.aerospike.client.Log; +import com.aerospike.client.Operation; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.ScanCallback; +import com.aerospike.client.Value; +import com.aerospike.client.admin.Privilege; +import com.aerospike.client.admin.Role; +import com.aerospike.client.admin.User; +import com.aerospike.client.async.EventLoop; +import com.aerospike.client.async.NettyEventLoop; +import com.aerospike.client.async.NettyEventLoops; +import com.aerospike.client.cdt.CTX; +import com.aerospike.client.cluster.Cluster; +import com.aerospike.client.cluster.ClusterStats; +import com.aerospike.client.cluster.Node; +import com.aerospike.client.cluster.ThreadDaemonFactory; +import com.aerospike.client.command.BatchAttr; +import com.aerospike.client.command.Command; +import com.aerospike.client.command.OperateArgs; +import com.aerospike.client.exp.Expression; +import com.aerospike.client.listener.BatchListListener; +import com.aerospike.client.listener.BatchOperateListListener; +import com.aerospike.client.listener.BatchRecordArrayListener; +import com.aerospike.client.listener.BatchRecordSequenceListener; +import com.aerospike.client.listener.BatchSequenceListener; +import com.aerospike.client.listener.ClusterStatsListener; +import com.aerospike.client.listener.DeleteListener; +import com.aerospike.client.listener.ExecuteListener; +import com.aerospike.client.listener.ExistsArrayListener; +import com.aerospike.client.listener.ExistsListener; +import com.aerospike.client.listener.ExistsSequenceListener; +import com.aerospike.client.listener.IndexListener; +import com.aerospike.client.listener.InfoListener; +import com.aerospike.client.listener.RecordArrayListener; +import com.aerospike.client.listener.RecordListener; +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.listener.WriteListener; +import com.aerospike.client.metrics.MetricsPolicy; +import com.aerospike.client.policy.AdminPolicy; +import com.aerospike.client.policy.BatchDeletePolicy; +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.policy.BatchUDFPolicy; +import com.aerospike.client.policy.BatchWritePolicy; +import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.policy.InfoPolicy; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.BatchProxy.BatchListListenerSync; +import com.aerospike.client.proxy.auth.AuthTokenManager; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcChannelProvider; +import com.aerospike.client.proxy.grpc.GrpcClientPolicy; +import com.aerospike.client.query.IndexCollectionType; +import com.aerospike.client.query.IndexType; +import com.aerospike.client.query.PartitionFilter; +import com.aerospike.client.query.PartitionTracker; +import com.aerospike.client.query.QueryListener; +import com.aerospike.client.query.RecordSet; +import com.aerospike.client.query.ResultSet; +import com.aerospike.client.query.Statement; +import com.aerospike.client.task.ExecuteTask; +import com.aerospike.client.task.IndexTask; +import com.aerospike.client.task.RegisterTask; +import com.aerospike.client.util.Packer; +import com.aerospike.client.util.Util; + +import io.netty.channel.Channel; + +/** + * Aerospike proxy client based implementation of {@link IAerospikeClient}. The proxy client + * communicates with a proxy server via GRPC and HTTP/2. The proxy server relays the database + * commands to the Aerospike server. The proxy client does not have knowledge of Aerospike + * server nodes. Only the proxy server can communicate directly with Aerospike server nodes. + * + * GRPC is an async framework, so an Aerospike sync command schedules the corresponding + * async command and then waits for the async command to complete before returning the data + * to the user. + * + * The async methods` eventLoop argument is ignored in the proxy client. Instead, the + * commands are pipelined into blocks which are then executed via one of multiple GRPC channels. + * Since the eventLoop thread is not chosen, results can be returned from different threads. + * If data is shared between multiple async command listeners, that data must be accessed in + * a thread-safe manner. + */ +public class AerospikeClientProxy implements IAerospikeClient, Closeable { + //------------------------------------------------------- + // Static variables. + //------------------------------------------------------- + + /** + * Proxy client version + */ + public static String Version = getVersion(); + + /** + * Lower limit of proxy server connection. + */ + private static final int MIN_CONNECTIONS = 1; + + /** + * Is threadPool shared between other client instances or classes. If threadPool is + * not shared (default), threadPool will be shutdown when the client instance is closed. + *

+ * If threadPool is shared, threadPool will not be shutdown when the client instance is + * closed. This shared threadPool should be shutdown manually before the program + * terminates. Shutdown is recommended, but not absolutely required if threadPool is + * constructed to use daemon threads. + *

+ * Default: false + */ + private final boolean sharedThreadPool; + + /** + * Underlying thread pool used in synchronous batch, scan, and query commands. These commands + * are often sent to multiple server nodes in parallel threads. A thread pool improves + * performance because threads do not have to be created/destroyed for each command. + * The default, null, indicates that the following daemon thread pool will be used: + *

+	 * threadPool = Executors.newCachedThreadPool(new ThreadFactory() {
+	 *     public final Thread newThread(Runnable runnable) {
+	 * 			Thread thread = new Thread(runnable);
+	 * 			thread.setDaemon(true);
+	 * 			return thread;
+	 *        }
+	 *    });
+	 * 
+ * Daemon threads automatically terminate when the program terminates. + *

+ * Default: null (use Executors.newCachedThreadPool) + */ + private final ExecutorService threadPool; + + /** + * Upper limit of proxy server connection. + */ + private static final int MAX_CONNECTIONS = 8; + + private static final String NotSupported = "Method not supported in proxy client: "; + + //------------------------------------------------------- + // Member variables. + //------------------------------------------------------- + + /** + * Default read policy that is used when read command policy is null. + */ + public final Policy readPolicyDefault; + + /** + * Default write policy that is used when write command policy is null. + */ + public final WritePolicy writePolicyDefault; + + /** + * Default scan policy that is used when scan command policy is null. + */ + public final ScanPolicy scanPolicyDefault; + + /** + * Default query policy that is used when query command policy is null. + */ + public final QueryPolicy queryPolicyDefault; + + /** + * Default parent policy used in batch read commands. Parent policy fields + * include socketTimeout, totalTimeout, maxRetries, etc... + */ + public final BatchPolicy batchPolicyDefault; + + /** + * Default parent policy used in batch write commands. Parent policy fields + * include socketTimeout, totalTimeout, maxRetries, etc... + */ + public final BatchPolicy batchParentPolicyWriteDefault; + + /** + * Default write policy used in batch operate commands. + * Write policy fields include generation, expiration, durableDelete, etc... + */ + public final BatchWritePolicy batchWritePolicyDefault; + + /** + * Default delete policy used in batch delete commands. + */ + public final BatchDeletePolicy batchDeletePolicyDefault; + + /** + * Default user defined function policy used in batch UDF execute commands. + */ + public final BatchUDFPolicy batchUDFPolicyDefault; + + /** + * Default info policy that is used when info command policy is null. + */ + public final InfoPolicy infoPolicyDefault; + + private final WritePolicy operatePolicyReadDefault; + private final AuthTokenManager authTokenManager; + private final GrpcCallExecutor executor; + + //------------------------------------------------------- + // Constructors + //------------------------------------------------------- + + /** + * Initialize proxy client with suitable hosts to seed the cluster map. + * The client policy is used to set defaults and size internal data structures. + *

+ * In most cases, only one host is necessary to seed the cluster. The remaining hosts + * are added as future seeds in case of a complete network failure. + * + * @param policy client configuration parameters, pass in null for defaults + * @param hosts array of potential hosts to seed the cluster + * @throws AerospikeException if all host connections fail + */ + public AerospikeClientProxy(ClientPolicy policy, Host... hosts) { + if (policy == null) { + policy = new ClientPolicy(); + policy.minConnsPerNode = 1; + policy.maxConnsPerNode = 8; + policy.asyncMaxConnsPerNode = 8; + policy.timeout = 5000; + } + + if (policy.threadPool == null) { + threadPool = Executors.newCachedThreadPool(new ThreadDaemonFactory()); + } + else { + threadPool = policy.threadPool; + } + sharedThreadPool = policy.sharedThreadPool; + + this.readPolicyDefault = policy.readPolicyDefault; + this.writePolicyDefault = policy.writePolicyDefault; + this.scanPolicyDefault = policy.scanPolicyDefault; + this.queryPolicyDefault = policy.queryPolicyDefault; + this.batchPolicyDefault = policy.batchPolicyDefault; + this.batchParentPolicyWriteDefault = policy.batchParentPolicyWriteDefault; + this.batchWritePolicyDefault = policy.batchWritePolicyDefault; + this.batchDeletePolicyDefault = policy.batchDeletePolicyDefault; + this.batchUDFPolicyDefault = policy.batchUDFPolicyDefault; + this.infoPolicyDefault = policy.infoPolicyDefault; + this.operatePolicyReadDefault = new WritePolicy(this.readPolicyDefault); + + GrpcChannelProvider channelProvider = new GrpcChannelProvider(); + + if (policy.user != null || policy.password != null) { + authTokenManager = new AuthTokenManager(policy, channelProvider); + } + else { + authTokenManager = null; + } + + try { + // The gRPC client policy transformed from the client policy. + GrpcClientPolicy grpcClientPolicy = toGrpcClientPolicy(policy); + executor = new GrpcCallExecutor(grpcClientPolicy, authTokenManager, hosts); + channelProvider.setCallExecutor(executor); + + // Warmup after the call executor in the channel provider has + // been set. The channel provider is used to fetch auth tokens + // required for the warm up calls. + executor.warmupChannels(); + } + catch (Throwable e) { + if(authTokenManager != null) { + authTokenManager.close(); + } + throw e; + } + } + + /** + * Return client version string. + */ + private static String getVersion() { + final Properties properties = new Properties(); + String version = null; + + try { + properties.load(AerospikeClientProxy.class.getClassLoader().getResourceAsStream("project.properties")); + version = properties.getProperty("version"); + } + catch (Exception e) { + Log.warn("Failed to retrieve client version: " + Util.getErrorMessage(e)); + } + return version; + } + + //------------------------------------------------------- + // Default Policies + //------------------------------------------------------- + + public final Policy getReadPolicyDefault() { + return readPolicyDefault; + } + + public final WritePolicy getWritePolicyDefault() { + return writePolicyDefault; + } + + public final ScanPolicy getScanPolicyDefault() { + return scanPolicyDefault; + } + + public final QueryPolicy getQueryPolicyDefault() { + return queryPolicyDefault; + } + + public final BatchPolicy getBatchPolicyDefault() { + return batchPolicyDefault; + } + + public final BatchPolicy getBatchParentPolicyWriteDefault() { + return batchParentPolicyWriteDefault; + } + + public final BatchWritePolicy getBatchWritePolicyDefault() { + return batchWritePolicyDefault; + } + + public final BatchDeletePolicy getBatchDeletePolicyDefault() { + return batchDeletePolicyDefault; + } + + public final BatchUDFPolicy getBatchUDFPolicyDefault() { + return batchUDFPolicyDefault; + } + + public final InfoPolicy getInfoPolicyDefault() { + return infoPolicyDefault; + } + + //------------------------------------------------------- + // Client Management + //------------------------------------------------------- + + /** + * Close GRPC executor and associated resources. The client instance should not + * be used after this call. + */ + @Override + public void close() { + try { + executor.close(); + } + catch (Throwable e) { + Log.warn("Failed to close grpcCallExecutor: " + Util.getErrorMessage(e)); + } + + try { + if (authTokenManager != null) { + authTokenManager.close(); + } + } + catch (Throwable e) { + Log.warn("Failed to close authTokenManager: " + Util.getErrorMessage(e)); + } + + if (! sharedThreadPool) { + // Shutdown synchronous thread pool. + threadPool.shutdown(); + } + } + + /** + * This method will always return true in the proxy client. + */ + @Override + public boolean isConnected() { + return executor != null; + } + + /** + * Not supported in proxy client. + */ + @Override + public Node[] getNodes() { + throw new AerospikeException(NotSupported + "getNodes"); + } + + /** + * Not supported in proxy client. + */ + @Override + public List getNodeNames() { + throw new AerospikeException(NotSupported + "getNodeNames"); + } + + /** + * Not supported in proxy client. + */ + @Override + public Node getNode(String nodeName) { + throw new AerospikeException(NotSupported + "getNode"); + } + + /** + * Not supported in proxy client. + */ + public final void enableMetrics(MetricsPolicy policy) { + throw new AerospikeException(NotSupported + "enableMetrics"); + } + + /** + * Not supported in proxy client. + */ + public final void disableMetrics() { + throw new AerospikeException(NotSupported + "disableMetrics"); + } + + /** + * Not supported in proxy client. + */ + @Override + public ClusterStats getClusterStats() { + throw new AerospikeException(NotSupported + "getClusterStats"); + } + + /** + * Not supported in proxy client. + */ + public final void getClusterStats(ClusterStatsListener listener) { + throw new AerospikeException(NotSupported + "getClusterStats"); + } + + /** + * Not supported in proxy client. + */ + @Override + public Cluster getCluster() { + throw new AerospikeException(NotSupported + "getCluster"); + } + + //------------------------------------------------------- + // Write Record Operations + //------------------------------------------------------- + + /** + * Write record bin(s). + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if write fails + */ + @Override + public void put(WritePolicy policy, Key key, Bin... bins) { + CompletableFuture future = new CompletableFuture<>(); + WriteListener listener = prepareWriteListener(future); + put(null, listener, policy, key, bins); + getFuture(future); + } + + /** + * Asynchronously write record bin(s). + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if event loop registration fails + */ + @Override + public void put(EventLoop eventLoop, WriteListener listener, WritePolicy policy, Key key, Bin... bins) { + if (policy == null) { + policy = writePolicyDefault; + } + WriteCommandProxy command = new WriteCommandProxy(executor, listener, policy, key, bins, Operation.Type.WRITE); + command.execute(); + } + + //------------------------------------------------------- + // String Operations + //------------------------------------------------------- + + /** + * Append bin string values to existing record bin values. + * This call only works for string values. + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if append fails + */ + @Override + public void append(WritePolicy policy, Key key, Bin... bins) { + CompletableFuture future = new CompletableFuture<>(); + WriteListener listener = prepareWriteListener(future); + append(null, listener, policy, key, bins); + getFuture(future); + } + + /** + * Asynchronously append bin string values to existing record bin values. + * This call only works for string values. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if event loop registration fails + */ + @Override + public void append(EventLoop eventLoop, WriteListener listener, WritePolicy policy, Key key, Bin... bins) { + if (policy == null) { + policy = writePolicyDefault; + } + WriteCommandProxy command = new WriteCommandProxy(executor, listener, policy, key, bins, Operation.Type.APPEND); + command.execute(); + } + + /** + * Prepend bin string values to existing record bin values. + * This call works only for string values. + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if prepend fails + */ + @Override + public void prepend(WritePolicy policy, Key key, Bin... bins) { + CompletableFuture future = new CompletableFuture<>(); + WriteListener listener = prepareWriteListener(future); + prepend(null, listener, policy, key, bins); + getFuture(future); + } + + /** + * Asynchronously prepend bin string values to existing record bin values. + * This call only works for string values. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if event loop registration fails + */ + @Override + public void prepend(EventLoop eventLoop, WriteListener listener, WritePolicy policy, Key key, Bin... bins) { + if (policy == null) { + policy = writePolicyDefault; + } + WriteCommandProxy command = new WriteCommandProxy(executor, listener, policy, key, bins, Operation.Type.PREPEND); + command.execute(); + } + + //------------------------------------------------------- + // Arithmetic Operations + //------------------------------------------------------- + + /** + * Add integer/double bin values to existing record bin values. + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if add fails + */ + @Override + public void add(WritePolicy policy, Key key, Bin... bins) { + CompletableFuture future = new CompletableFuture<>(); + WriteListener listener = prepareWriteListener(future); + add(null, listener, policy, key, bins); + getFuture(future); + } + + /** + * Asynchronously add integer/double bin values to existing record bin values. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param bins array of bin name/value pairs + * @throws AerospikeException if event loop registration fails + */ + @Override + public void add(EventLoop eventLoop, WriteListener listener, WritePolicy policy, Key key, Bin... bins) { + if (policy == null) { + policy = writePolicyDefault; + } + WriteCommandProxy command = new WriteCommandProxy(executor, listener, policy, key, bins, Operation.Type.ADD); + command.execute(); + } + + //------------------------------------------------------- + // Delete Operations + //------------------------------------------------------- + + /** + * Delete record for specified key. + * + * @param policy delete configuration parameters, pass in null for defaults + * @param key unique record identifier + * @return whether record existed on server before deletion + * @throws AerospikeException if delete fails + */ + @Override + public boolean delete(WritePolicy policy, Key key) { + CompletableFuture future = new CompletableFuture<>(); + DeleteListener listener = prepareDeleteListener(future); + delete(null, listener, policy, key); + return getFuture(future); + } + + /** + * Asynchronously delete record for specified key. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if event loop registration fails + */ + @Override + public void delete(EventLoop eventLoop, DeleteListener listener, WritePolicy policy, Key key) { + if (policy == null) { + policy = writePolicyDefault; + } + DeleteCommandProxy command = new DeleteCommandProxy(executor, listener, policy, key); + command.execute(); + } + + /** + * Delete records for specified keys. If a key is not found, the corresponding result + * {@link BatchRecord#resultCode} will be {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param deletePolicy delete configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException.BatchRecordArray which contains results for keys that did complete + */ + @Override + public BatchResults delete(BatchPolicy batchPolicy, BatchDeletePolicy deletePolicy, Key[] keys) { + CompletableFuture future = new CompletableFuture<>(); + BatchRecordArrayListener listener = prepareBatchRecordArrayListener(future); + delete(null, listener, batchPolicy, deletePolicy, keys); + return getFuture(future); + } + + /** + * Asynchronously delete records for specified keys. + *

+ * If a key is not found, the corresponding result {@link BatchRecord#resultCode} will be + * {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param deletePolicy delete configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void delete( + EventLoop eventLoop, + BatchRecordArrayListener listener, + BatchPolicy batchPolicy, + BatchDeletePolicy deletePolicy, + Key[] keys + ) { + if (keys.length == 0) { + listener.onSuccess(new BatchRecord[0], true); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (deletePolicy == null) { + deletePolicy = batchDeletePolicyDefault; + } + + BatchAttr attr = new BatchAttr(); + attr.setDelete(deletePolicy); + + CommandProxy command = new BatchProxy.OperateRecordArrayCommand(executor, + batchPolicy, keys, null, listener, attr); + + command.execute(); + } + + /** + * Asynchronously delete records for specified keys. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the corresponding result {@link BatchRecord#resultCode} will be + * {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param deletePolicy delete configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void delete( + EventLoop eventLoop, + BatchRecordSequenceListener listener, + BatchPolicy batchPolicy, + BatchDeletePolicy deletePolicy, + Key[] keys + ) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (deletePolicy == null) { + deletePolicy = batchDeletePolicyDefault; + } + + BatchAttr attr = new BatchAttr(); + attr.setDelete(deletePolicy); + + CommandProxy command = new BatchProxy.OperateRecordSequenceCommand(executor, + batchPolicy, keys, null, listener, attr); + + command.execute(); + } + + /** + * Not supported in proxy client. + */ + @Override + public void truncate(InfoPolicy policy, String ns, String set, Calendar beforeLastUpdate) { + throw new AerospikeException(NotSupported + "truncate"); + } + + //------------------------------------------------------- + // Touch Operations + //------------------------------------------------------- + + /** + * Reset record's time to expiration using the policy's expiration. + * Fail if the record does not exist. + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if touch fails + */ + @Override + public void touch(WritePolicy policy, Key key) { + CompletableFuture future = new CompletableFuture<>(); + WriteListener listener = prepareWriteListener(future); + touch(null, listener, policy, key); + getFuture(future); + } + + /** + * Asynchronously reset record's time to expiration using the policy's expiration. + * Fail if the record does not exist. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if event loop registration fails + */ + @Override + public void touch(EventLoop eventLoop, WriteListener listener, WritePolicy policy, Key key) { + if (policy == null) { + policy = writePolicyDefault; + } + TouchCommandProxy command = new TouchCommandProxy(executor, listener, policy, key); + command.execute(); + } + + //------------------------------------------------------- + // Existence-Check Operations + //------------------------------------------------------- + + /** + * Determine if a record key exists. + * + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @return whether record exists or not + * @throws AerospikeException if command fails + */ + @Override + public boolean exists(Policy policy, Key key) { + CompletableFuture future = new CompletableFuture<>(); + ExistsListener listener = prepareExistsListener(future); + exists(null, listener, policy, key); + return getFuture(future); + } + + /** + * Asynchronously determine if a record key exists. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if event loop registration fails + */ + @Override + public void exists(EventLoop eventLoop, ExistsListener listener, Policy policy, Key key) { + if (policy == null) { + policy = readPolicyDefault; + } + ExistsCommandProxy command = new ExistsCommandProxy(executor, listener, policy, key); + command.execute(); + } + + /** + * Check if multiple record keys exist in one batch call. + * The returned boolean array is in positional order with the original key array order. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @return array key/existence status pairs + * @throws AerospikeException.BatchExists which contains results for keys that did complete + */ + @Override + public boolean[] exists(BatchPolicy policy, Key[] keys) { + CompletableFuture future = new CompletableFuture<>(); + ExistsArrayListener listener = prepareExistsArrayListener(future); + exists(null, listener, policy, keys); + return getFuture(future); + } + + /** + * Asynchronously check if multiple record keys exist in one batch call. + *

+ * The returned boolean array is in positional order with the original key array order. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void exists(EventLoop eventLoop, ExistsArrayListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(keys, new boolean[0]); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.ExistsArrayCommand(executor, policy, listener, keys); + command.execute(); + } + + /** + * Asynchronously check if multiple record keys exist in one batch call. + *

+ * Each key's result is returned in separate onExists() calls. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void exists(EventLoop eventLoop, ExistsSequenceListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.ExistsSequenceCommand(executor, policy, listener, keys); + command.execute(); + } + + //------------------------------------------------------- + // Read Record Operations + //------------------------------------------------------- + + /** + * Read entire record for specified key. + * + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @return if found, return record instance. If not found, return null. + * @throws AerospikeException if read fails + */ + @Override + public Record get(Policy policy, Key key) { + return get(policy, key, (String[])null); + } + + /** + * Asynchronously read entire record for specified key. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordListener listener, Policy policy, Key key) { + get(eventLoop, listener, policy, key, (String[])null); + } + + /** + * Read record header and bins for specified key. + * + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param binNames bins to retrieve + * @return if found, return record instance. If not found, return null. + * @throws AerospikeException if read fails + */ + @Override + public Record get(Policy policy, Key key, String... binNames) { + CompletableFuture future = new CompletableFuture<>(); + RecordListener listener = prepareRecordListener(future); + get(null, listener, policy, key, binNames); + return getFuture(future); + } + + /** + * Asynchronously read record header and bins for specified key. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param binNames bins to retrieve + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordListener listener, Policy policy, Key key, String... binNames) { + if (policy == null) { + policy = readPolicyDefault; + } + ReadCommandProxy command = new ReadCommandProxy(executor, listener, policy, key, binNames); + command.execute(); + } + + /** + * Read record generation and expiration only for specified key. Bins are not read. + * + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @return if found, return record instance. If not found, return null. + * @throws AerospikeException if read fails + */ + @Override + public Record getHeader(Policy policy, Key key) { + CompletableFuture future = new CompletableFuture<>(); + RecordListener listener = prepareRecordListener(future); + getHeader(null, listener, policy, key); + return getFuture(future); + } + + /** + * Asynchronously read record generation and expiration only for specified key. Bins are not read. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy generic configuration parameters, pass in null for defaults + * @param key unique record identifier + * @throws AerospikeException if event loop registration fails + */ + @Override + public void getHeader(EventLoop eventLoop, RecordListener listener, Policy policy, Key key) { + if (policy == null) { + policy = readPolicyDefault; + } + ReadHeaderCommandProxy command = new ReadHeaderCommandProxy(executor, listener, policy, key); + command.execute(); + } + + //------------------------------------------------------- + // Batch Read Operations + //------------------------------------------------------- + + /** + * Read multiple records for specified batch keys in one batch call. + * This method allows different namespaces/bins to be requested for each key in the batch. + * The returned records are located in the same list. + * If the BatchRead key field is not found, the corresponding record field will be null. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and the bins to retrieve. + * The returned records are located in the same list. + * @return true if all batch key requests succeeded + * @throws AerospikeException if read fails + */ + @Override + public boolean get(BatchPolicy policy, List records) { + if (records.size() == 0) { + return true; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CompletableFuture future = new CompletableFuture<>(); + BatchListListenerSync listener = prepareBatchListListenerSync(future); + + CommandProxy command = new BatchProxy.ReadListCommandSync(executor, policy, listener, records); + command.execute(); + + return getFuture(future); + } + + /** + * Asynchronously read multiple records for specified batch keys in one batch call. + *

+ * This method allows different namespaces/bins to be requested for each key in the batch. + * The returned records are located in the same list. + * If the BatchRead key field is not found, the corresponding record field will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and the bins to retrieve. + * The returned records are located in the same list. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, BatchListListener listener, BatchPolicy policy, List records) { + if (records.size() == 0) { + listener.onSuccess(records); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + CommandProxy command = new BatchProxy.ReadListCommand(executor, policy, listener, records); + command.execute(); + } + + /** + * Asynchronously read multiple records for specified batch keys in one batch call. + *

+ * This method allows different namespaces/bins to be requested for each key in the batch. + * Each record result is returned in separate onRecord() calls. + * If the BatchRead key field is not found, the corresponding record field will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and the bins to retrieve. + * The returned records are located in the same list. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, BatchSequenceListener listener, BatchPolicy policy, List records) { + if (records.size() == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.ReadSequenceCommand(executor, policy, listener, records); + command.execute(); + } + + /** + * Read multiple records for specified keys in one batch call. + * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @return array of records + * @throws AerospikeException.BatchRecords which contains results for keys that did complete + */ + @Override + public Record[] get(BatchPolicy policy, Key[] keys) { + CompletableFuture future = new CompletableFuture<>(); + RecordArrayListener listener = prepareRecordArrayListener(future); + get(null, listener, policy, keys); + return getFuture(future); + } + + /** + * Asynchronously read multiple records for specified keys in one batch call. + *

+ * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordArrayListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(keys, new Record[0]); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetArrayCommand(executor, policy, listener, keys, null, null, Command.INFO1_READ | Command.INFO1_GET_ALL, false); + command.execute(); + } + + /** + * Asynchronously read multiple records for specified keys in one batch call. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordSequenceListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetSequenceCommand(executor, policy, listener, keys, null, null, Command.INFO1_READ | Command.INFO1_GET_ALL, false); + command.execute(); + } + + /** + * Read multiple record headers and bins for specified keys in one batch call. + * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param binNames array of bins to retrieve + * @return array of records + * @throws AerospikeException.BatchRecords which contains results for keys that did complete + */ + @Override + public Record[] get(BatchPolicy policy, Key[] keys, String... binNames) { + CompletableFuture future = new CompletableFuture<>(); + RecordArrayListener listener = prepareRecordArrayListener(future); + get(null, listener, policy, keys, binNames); + return getFuture(future); + } + + /** + * Asynchronously read multiple record headers and bins for specified keys in one batch call. + *

+ * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param binNames array of bins to retrieve + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordArrayListener listener, BatchPolicy policy, Key[] keys, String... binNames) { + if (keys.length == 0) { + listener.onSuccess(keys, new Record[0]); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetArrayCommand(executor, policy, listener, keys, binNames, null, Command.INFO1_READ, false); + command.execute(); + } + + /** + * Asynchronously read multiple record headers and bins for specified keys in one batch call. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param binNames array of bins to retrieve + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordSequenceListener listener, BatchPolicy policy, Key[] keys, String... binNames) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetSequenceCommand(executor, policy, listener, keys, binNames, null, Command.INFO1_READ, false); + command.execute(); + } + + /** + * Read multiple records for specified keys using read operations in one batch call. + * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops array of read operations on record + * @return array of records + * @throws AerospikeException.BatchRecords which contains results for keys that did complete + */ + @Override + public Record[] get(BatchPolicy policy, Key[] keys, Operation... ops) { + CompletableFuture future = new CompletableFuture<>(); + RecordArrayListener listener = prepareRecordArrayListener(future); + get(null, listener, policy, keys, ops); + return getFuture(future); + } + + /** + * Asynchronously read multiple records for specified keys using read operations in one batch call. + *

+ * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops array of read operations on record + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordArrayListener listener, BatchPolicy policy, Key[] keys, Operation... ops) { + if (keys.length == 0) { + listener.onSuccess(keys, new Record[0]); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetArrayCommand(executor, policy, listener, keys, null, ops, Command.INFO1_READ, true); + command.execute(); + } + + /** + * Asynchronously read multiple records for specified keys using read operations in one batch call. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops array of read operations on record + * @throws AerospikeException if event loop registration fails + */ + @Override + public void get(EventLoop eventLoop, RecordSequenceListener listener, BatchPolicy policy, Key[] keys, Operation... ops) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetSequenceCommand(executor, policy, listener, keys, null, ops, Command.INFO1_READ, true); + command.execute(); + } + + /** + * Read multiple record header data for specified keys in one batch call. + * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @return array of records + * @throws AerospikeException.BatchRecords which contains results for keys that did complete + */ + @Override + public Record[] getHeader(BatchPolicy policy, Key[] keys) { + CompletableFuture future = new CompletableFuture<>(); + RecordArrayListener listener = prepareRecordArrayListener(future); + getHeader(null, listener, policy, keys); + return getFuture(future); + } + + /** + * Asynchronously read multiple record header data for specified keys in one batch call. + *

+ * The returned records are in positional order with the original key array order. + * If a key is not found, the positional record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void getHeader(EventLoop eventLoop, RecordArrayListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(keys, new Record[0]); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetArrayCommand(executor, policy, listener, keys, null, null, Command.INFO1_READ | Command.INFO1_NOBINDATA, false); + command.execute(); + } + + /** + * Asynchronously read multiple record header data for specified keys in one batch call. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the record will be null. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @throws AerospikeException if event loop registration fails + */ + @Override + public void getHeader(EventLoop eventLoop, RecordSequenceListener listener, BatchPolicy policy, Key[] keys) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchPolicyDefault; + } + + CommandProxy command = new BatchProxy.GetSequenceCommand(executor, policy, listener, keys, null, null, Command.INFO1_READ | Command.INFO1_NOBINDATA, false); + command.execute(); + } + + //------------------------------------------------------- + // Generic Database Operations + //------------------------------------------------------- + + /** + * Perform multiple read/write operations on a single key in one batch call. + * An example would be to add an integer value to an existing record and then + * read the result, all in one database call. + *

+ * The server executes operations in the same order as the operations array. + * Both scalar bin operations (Operation) and CDT bin operations (ListOperation, + * MapOperation) can be performed in same call. + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param operations database operations to perform + * @return record if there is a read in the operations list + * @throws AerospikeException if command fails + */ + @Override + public Record operate(WritePolicy policy, Key key, Operation... operations) { + CompletableFuture future = new CompletableFuture<>(); + RecordListener listener = prepareRecordListener(future); + operate(null, listener, policy, key, operations); + return getFuture(future); + } + + /** + * Asynchronously perform multiple read/write operations on a single key in one batch call. + *

+ * An example would be to add an integer value to an existing record and then + * read the result, all in one database call. + *

+ * The server executes operations in the same order as the operations array. + * Both scalar bin operations (Operation) and CDT bin operations (ListOperation, + * MapOperation) can be performed in same call. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param operations database operations to perform + * @throws AerospikeException if event loop registration fails + */ + @Override + public void operate(EventLoop eventLoop, RecordListener listener, WritePolicy policy, Key key, Operation... operations) { + OperateArgs args = new OperateArgs(policy, writePolicyDefault, operatePolicyReadDefault, key, operations); + OperateCommandProxy command = new OperateCommandProxy(executor, listener, args.writePolicy, key, args); + command.execute(); + } + + //------------------------------------------------------- + // Batch Read/Write Operations + //------------------------------------------------------- + + /** + * Read/Write multiple records for specified batch keys in one batch call. + * This method allows different namespaces/bins for each key in the batch. + * The returned records are located in the same list. + *

+ * {@link BatchRecord} can be {@link BatchRead}, {@link BatchWrite}, {@link BatchDelete} or + * {@link BatchUDF}. + * + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and read/write operations + * @return true if all batch sub-commands succeeded + * @throws AerospikeException if command fails + */ + @Override + public boolean operate(BatchPolicy policy, List records) { + CompletableFuture future = new CompletableFuture<>(); + BatchOperateListListener listener = prepareBatchOperateListListener(future); + operate(null, listener, policy, records); + return getFuture(future); + } + + /** + * Asynchronously read/write multiple records for specified batch keys in one batch call. + *

+ * This method allows different namespaces/bins to be requested for each key in the batch. + * The returned records are located in the same list. + *

+ * {@link BatchRecord} can be {@link BatchRead}, {@link BatchWrite}, {@link BatchDelete} or + * {@link BatchUDF}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and read/write operations + * @throws AerospikeException if event loop registration fails + */ + @Override + public void operate( + EventLoop eventLoop, + BatchOperateListListener listener, + BatchPolicy policy, + List records + ) { + if (records.size() == 0) { + listener.onSuccess(records, true); + return; + } + + if (policy == null) { + policy = batchParentPolicyWriteDefault; + } + + CommandProxy command = new BatchProxy.OperateListCommand(executor, policy, listener, records); + command.execute(); + } + + /** + * Asynchronously read/write multiple records for specified batch keys in one batch call. + *

+ * This method allows different namespaces/bins to be requested for each key in the batch. + * Each record result is returned in separate onRecord() calls. + *

+ * {@link BatchRecord} can be {@link BatchRead}, {@link BatchWrite}, {@link BatchDelete} or + * {@link BatchUDF}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy batch configuration parameters, pass in null for defaults + * @param records list of unique record identifiers and read/write operations + * @throws AerospikeException if event loop registration fails + */ + @Override + public void operate( + EventLoop eventLoop, + BatchRecordSequenceListener listener, + BatchPolicy policy, + List records + ) { + if (records.size() == 0) { + listener.onSuccess(); + return; + } + + if (policy == null) { + policy = batchParentPolicyWriteDefault; + } + + CommandProxy command = new BatchProxy.OperateSequenceCommand(executor, policy, listener, records); + command.execute(); + } + + /** + * Perform read/write operations on multiple keys. If a key is not found, the corresponding result + * {@link BatchRecord#resultCode} will be {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param writePolicy write configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops + * read/write operations to perform. {@link Operation#get()} is not allowed because it returns a + * variable number of bins and makes it difficult (sometimes impossible) to lineup operations + * with results. Instead, use {@link Operation#get(String)} for each bin name. + * @throws AerospikeException.BatchRecordArray which contains results for keys that did complete + */ + @Override + public BatchResults operate( + BatchPolicy batchPolicy, + BatchWritePolicy writePolicy, + Key[] keys, + Operation... ops + ) { + CompletableFuture future = new CompletableFuture<>(); + BatchRecordArrayListener listener = prepareBatchRecordArrayListener(future); + operate(null, listener, batchPolicy, writePolicy, keys, ops); + return getFuture(future); + } + + /** + * Asynchronously perform read/write operations on multiple keys. + *

+ * If a key is not found, the corresponding result {@link BatchRecord#resultCode} will be + * {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param writePolicy write configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops + * read/write operations to perform. {@link Operation#get()} is not allowed because it returns a + * variable number of bins and makes it difficult (sometimes impossible) to lineup operations + * with results. Instead, use {@link Operation#get(String)} for each bin name. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void operate( + EventLoop eventLoop, + BatchRecordArrayListener listener, + BatchPolicy batchPolicy, + BatchWritePolicy writePolicy, + Key[] keys, + Operation... ops + ) { + if (keys.length == 0) { + listener.onSuccess(new BatchRecord[0], true); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (writePolicy == null) { + writePolicy = batchWritePolicyDefault; + } + + BatchAttr attr = new BatchAttr(batchPolicy, writePolicy, ops); + + CommandProxy command = new BatchProxy.OperateRecordArrayCommand(executor, + batchPolicy, keys, ops, listener, attr); + + command.execute(); + } + + /** + * Asynchronously perform read/write operations on multiple keys. + *

+ * Each record result is returned in separate onRecord() calls. + * If a key is not found, the corresponding result {@link BatchRecord#resultCode} will be + * {@link ResultCode#KEY_NOT_FOUND_ERROR}. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param writePolicy write configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param ops + * read/write operations to perform. {@link Operation#get()} is not allowed because it returns a + * variable number of bins and makes it difficult (sometimes impossible) to lineup operations + * with results. Instead, use {@link Operation#get(String)} for each bin name. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void operate( + EventLoop eventLoop, + BatchRecordSequenceListener listener, + BatchPolicy batchPolicy, + BatchWritePolicy writePolicy, + Key[] keys, + Operation... ops + ) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (writePolicy == null) { + writePolicy = batchWritePolicyDefault; + } + + BatchAttr attr = new BatchAttr(batchPolicy, writePolicy, ops); + + CommandProxy command = new BatchProxy.OperateRecordSequenceCommand(executor, + batchPolicy, keys, ops, listener, attr); + + command.execute(); + } + + //------------------------------------------------------- + // Scan Operations + //------------------------------------------------------- + + /** + * Read all records in specified namespace and set. + *

+ * This call will block until the scan is complete - callbacks are made + * within the scope of this call. + * + * @param policy scan configuration parameters, pass in null for defaults + * @param namespace namespace - equivalent to database name + * @param setName optional set name - equivalent to database table + * @param callback read callback method - called with record data + * @param binNames optional bin to retrieve. All bins will be returned if not specified. + * @throws AerospikeException if scan fails + */ + @Override + public void scanAll( + ScanPolicy policy, + String namespace, + String setName, + ScanCallback callback, + String... binNames + ) { + CompletableFuture future = new CompletableFuture<>(); + RecordSequenceListener listener = new RecordSequenceListenerToCallback(callback, future); + scanPartitions(null, listener, policy, null, namespace, setName, binNames); + getFuture(future); + } + + /** + * Asynchronously read all records in specified namespace and set. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy scan configuration parameters, pass in null for defaults + * @param namespace namespace - equivalent to database name + * @param setName optional set name - equivalent to database table + * @param binNames optional bin to retrieve. All bins will be returned if not specified. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void scanAll( + EventLoop eventLoop, + RecordSequenceListener listener, + ScanPolicy policy, + String namespace, + String setName, + String... binNames + ) { + scanPartitions(eventLoop, listener, policy, null, namespace, setName, binNames); + } + + /** + * Not supported in proxy client. + */ + @Override + public void scanNode( + ScanPolicy policy, + String nodeName, + String namespace, + String setName, + ScanCallback callback, + String... binNames + ) { + throw new AerospikeException(NotSupported + "scanNode"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void scanNode( + ScanPolicy policy, + Node node, + String namespace, + String setName, + ScanCallback callback, + String... binNames + ) { + throw new AerospikeException(NotSupported + "scanNode"); + } + + /** + * Read records in specified namespace, set and partition filter. + *

+ * This call will block until the scan is complete - callbacks are made + * within the scope of this call. + * + * @param policy scan configuration parameters, pass in null for defaults + * @param partitionFilter filter on a subset of data partitions + * @param namespace namespace - equivalent to database name + * @param setName optional set name - equivalent to database table + * @param callback read callback method - called with record data + * @param binNames optional bin to retrieve. All bins will be returned if not specified + * @throws AerospikeException if scan fails + */ + @Override + public void scanPartitions( + ScanPolicy policy, + PartitionFilter partitionFilter, + String namespace, + String setName, + ScanCallback callback, + String... binNames + ) { + CompletableFuture future = new CompletableFuture<>(); + RecordSequenceListener listener = new RecordSequenceListenerToCallback(callback, future); + scanPartitions(null, listener, policy, partitionFilter, namespace, setName, binNames); + getFuture(future); + } + + /** + * Asynchronously read records in specified namespace, set and partition filter. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy scan configuration parameters, pass in null for defaults + * @param partitionFilter filter on a subset of data partitions + * @param namespace namespace - equivalent to database name + * @param setName optional set name - equivalent to database table + * @param binNames optional bin to retrieve. All bins will be returned if not specified. + * @throws AerospikeException if event loop registration fails + */ + @Override + public void scanPartitions( + EventLoop eventLoop, + RecordSequenceListener listener, + ScanPolicy policy, + PartitionFilter partitionFilter, + String namespace, + String setName, + String... binNames + ) { + if (policy == null) { + policy = scanPolicyDefault; + } + + PartitionTracker tracker = null; + + if (partitionFilter != null) { + tracker = new PartitionTracker(policy, 1, partitionFilter); + } + + ScanCommandProxy command = new ScanCommandProxy(executor, policy, listener, namespace, + setName, binNames, partitionFilter, tracker); + command.execute(); + } + + //--------------------------------------------------------------- + // User defined functions + //--------------------------------------------------------------- + + /** + * Not supported in proxy client. + */ + @Override + public RegisterTask register(Policy policy, String clientPath, String serverPath, Language language) { + throw new AerospikeException(NotSupported + "register"); + } + + /** + * Not supported in proxy client. + */ + @Override + public RegisterTask register( + Policy policy, + ClassLoader resourceLoader, + String resourcePath, + String serverPath, + Language language + ) { + throw new AerospikeException(NotSupported + "register"); + } + + /** + * Not supported in proxy client. + */ + @Override + public RegisterTask registerUdfString(Policy policy, String code, String serverPath, Language language) { + throw new AerospikeException(NotSupported + "registerUdfString"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void removeUdf(InfoPolicy policy, String serverPath) { + throw new AerospikeException(NotSupported + "removeUdf"); + } + + /** + * Execute user defined function on server and return results. + * The function operates on a single record. + * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param packageName server package name where user defined function resides + * @param functionName user defined function + * @param functionArgs arguments passed in to user defined function + * @return return value of user defined function + * @throws AerospikeException if transaction fails + */ + @Override + public Object execute(WritePolicy policy, Key key, String packageName, String functionName, Value... functionArgs) { + CompletableFuture future = new CompletableFuture<>(); + ExecuteListener listener = prepareExecuteListener(future); + execute(null, listener, policy, key, packageName, functionName, functionArgs); + return getFuture(future); + } + + /** + * Asynchronously execute user defined function on server. + *

+ * The function operates on a single record. + * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param eventLoop ignored, pass in null + * @param listener where to send results, pass in null for fire and forget + * @param policy write configuration parameters, pass in null for defaults + * @param key unique record identifier + * @param packageName server package name where user defined function resides + * @param functionName user defined function + * @param functionArgs arguments passed in to user defined function + * @throws AerospikeException if event loop registration fails + */ + @Override + public void execute( + EventLoop eventLoop, + ExecuteListener listener, + WritePolicy policy, + Key key, + String packageName, + String functionName, + Value... functionArgs + ) { + if (policy == null) { + policy = writePolicyDefault; + } + ExecuteCommandProxy command = new ExecuteCommandProxy(executor, listener, policy, key, + packageName, functionName, functionArgs); + command.execute(); + } + + /** + * Execute user defined function on server for each key and return results. + * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param udfPolicy udf configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param packageName server package name where user defined function resides + * @param functionName user defined function + * @param functionArgs arguments passed in to user defined function + * @throws AerospikeException.BatchRecordArray which contains results for keys that did complete + */ + @Override + public BatchResults execute( + BatchPolicy batchPolicy, + BatchUDFPolicy udfPolicy, + Key[] keys, + String packageName, + String functionName, + Value... functionArgs + ) { + CompletableFuture future = new CompletableFuture<>(); + BatchRecordArrayListener listener = prepareBatchRecordArrayListener(future); + execute(null, listener, batchPolicy, udfPolicy, keys, packageName, functionName, functionArgs); + return getFuture(future); + } + + /** + * Asynchronously execute user defined function on server for each key and return results. + *

+ * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param udfPolicy udf configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param packageName server package name where user defined function resides + * @param functionName user defined function + * @param functionArgs arguments passed in to user defined function + * @throws AerospikeException if command fails + */ + @Override + public void execute( + EventLoop eventLoop, + BatchRecordArrayListener listener, + BatchPolicy batchPolicy, + BatchUDFPolicy udfPolicy, + Key[] keys, + String packageName, + String functionName, + Value... functionArgs + ) { + if (keys.length == 0) { + listener.onSuccess(new BatchRecord[0], true); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (udfPolicy == null) { + udfPolicy = batchUDFPolicyDefault; + } + + byte[] argBytes = Packer.pack(functionArgs); + + BatchAttr attr = new BatchAttr(); + attr.setUDF(udfPolicy); + + CommandProxy command = new BatchProxy.UDFArrayCommand(executor, batchPolicy, + listener, keys, packageName, functionName, argBytes, attr); + + command.execute(); + } + + /** + * Asynchronously execute user defined function on server for each key and return results. + * Each record result is returned in separate onRecord() calls. + *

+ * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param batchPolicy batch configuration parameters, pass in null for defaults + * @param udfPolicy udf configuration parameters, pass in null for defaults + * @param keys array of unique record identifiers + * @param packageName server package name where user defined function resides + * @param functionName user defined function + * @param functionArgs arguments passed in to user defined function + * @throws AerospikeException if command fails + */ + @Override + public void execute( + EventLoop eventLoop, + BatchRecordSequenceListener listener, + BatchPolicy batchPolicy, + BatchUDFPolicy udfPolicy, + Key[] keys, + String packageName, + String functionName, + Value... functionArgs + ) { + if (keys.length == 0) { + listener.onSuccess(); + return; + } + + if (batchPolicy == null) { + batchPolicy = batchParentPolicyWriteDefault; + } + + if (udfPolicy == null) { + udfPolicy = batchUDFPolicyDefault; + } + + byte[] argBytes = Packer.pack(functionArgs); + + BatchAttr attr = new BatchAttr(); + attr.setUDF(udfPolicy); + + CommandProxy command = new BatchProxy.UDFSequenceCommand(executor, batchPolicy, + listener, keys, packageName, functionName, argBytes, attr); + + command.execute(); + } + + //---------------------------------------------------------- + // Query/Execute + //---------------------------------------------------------- + + /** + * Apply user defined function on records that match the background query statement filter. + * Records are not returned to the client. + * This asynchronous server call will return before the command is complete. + * The user can optionally wait for command completion by using the returned + * ExecuteTask instance. + * + * @param policy write configuration parameters, pass in null for defaults + * @param statement background query definition + * @param packageName server package where user defined function resides + * @param functionName function name + * @param functionArgs to pass to function name, if any + * @throws AerospikeException if command fails + */ + @Override + public ExecuteTask execute( + WritePolicy policy, + Statement statement, + String packageName, + String functionName, + Value... functionArgs + ) { + statement.setAggregateFunction(packageName, functionName, functionArgs); + return executeBackgroundTask(policy, statement); + } + + /** + * Apply operations on records that match the background query statement filter. + * Records are not returned to the client. + * This asynchronous server call will return before the command is complete. + * The user can optionally wait for command completion by using the returned + * ExecuteTask instance. + * + * @param policy write configuration parameters, pass in null for defaults + * @param statement background query definition + * @param operations list of operations to be performed on selected records + * @throws AerospikeException if command fails + */ + @Override + public ExecuteTask execute(WritePolicy policy, Statement statement, Operation... operations) { + if (operations.length > 0) { + statement.setOperations(operations); + } + return executeBackgroundTask(policy, statement); + } + + //-------------------------------------------------------- + // Query functions + //-------------------------------------------------------- + + /** + * Execute query on all server nodes and return record iterator. The query executor puts + * records on a queue in separate threads. The calling thread concurrently pops records off + * the queue through the record iterator. + *

+ * This method is not recommended for paginated queries when the user does not iterate through + * all records in the RecordSet. In this case, there is a lag between when the client marks the + * last record retrieved from the server and when the record is retrieved from the RecordSet. + * For this case, use {@link #query(QueryPolicy, Statement, QueryListener)} which uses a listener + * callback (without a buffer) instead of a RecordSet. + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @return record iterator + * @throws AerospikeException if query fails + */ + @Override + public RecordSet query(QueryPolicy policy, Statement statement) { + if (policy == null) { + policy = queryPolicyDefault; + } + + // @Ashish taskId will be zero by default here. + RecordSequenceRecordSet recordSet = new RecordSequenceRecordSet(statement.getTaskId(), policy.recordQueueSize); + query(null, recordSet, policy, statement); + return recordSet; + } + + /** + * Asynchronously execute query on all server nodes. + *

+ * Each record result is returned in separate onRecord() calls. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @throws AerospikeException if event loop registration fails + */ + @Override + public void query(EventLoop eventLoop, RecordSequenceListener listener, QueryPolicy policy, Statement statement) { + if (policy == null) { + policy = queryPolicyDefault; + } + + long taskId = statement.prepareTaskId(); + QueryCommandProxy command = new QueryCommandProxy(executor, listener, + policy, statement, taskId, null, null); + command.execute(); + } + + /** + * Execute query on all server nodes and return records via the listener. This method will + * block until the query is complete. Listener callbacks are made within the scope of this call. + *

+ * If {@link com.aerospike.client.policy.QueryPolicy#maxConcurrentNodes} is not 1, the supplied + * listener must handle shared data in a thread-safe manner, because the listener will be called + * by multiple query threads (one thread per node) in parallel. + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @param listener where to send results + * @throws AerospikeException if query fails + */ + @Override + public void query(QueryPolicy policy, Statement statement, QueryListener listener) { + CompletableFuture future = new CompletableFuture<>(); + RecordSequenceToQueryListener adaptor = new RecordSequenceToQueryListener(listener, future); + query(null, adaptor, policy, statement); + getFuture(future); + } + + /** + * Execute query for specified partitions and return records via the listener. This method will + * block until the query is complete. Listener callbacks are made within the scope of this call. + *

+ * If {@link com.aerospike.client.policy.QueryPolicy#maxConcurrentNodes} is not 1, the supplied + * listener must handle shared data in a thread-safe manner, because the listener will be called + * by multiple query threads (one thread per node) in parallel. + *

+ * The completion status of all partitions is stored in the partitionFilter when the query terminates. + * This partitionFilter can then be used to resume an incomplete query at a later time. + * This is the preferred method for query terminate/resume functionality. + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @param partitionFilter data partition filter. Set to + * {@link com.aerospike.client.query.PartitionFilter#all()} for all partitions. + * @param listener where to send results + * @throws AerospikeException if query fails + */ + @Override + public void query(QueryPolicy policy, Statement statement, PartitionFilter partitionFilter, QueryListener listener) { + CompletableFuture future = new CompletableFuture<>(); + RecordSequenceToQueryListener adaptor = new RecordSequenceToQueryListener(listener, future); + queryPartitions(null, adaptor, policy, statement, partitionFilter); + getFuture(future); + } + + /** + * Not supported in proxy client. + */ + @Override + public RecordSet queryNode(QueryPolicy policy, Statement statement, Node node) { + throw new AerospikeException(NotSupported + "queryNode"); + } + + /** + * Execute query for specified partitions and return record iterator. The query executor puts + * records on a queue in separate threads. The calling thread concurrently pops records off + * the queue through the record iterator. + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @param partitionFilter filter on a subset of data partitions + * @throws AerospikeException if query fails + */ + @Override + public RecordSet queryPartitions(QueryPolicy policy, Statement statement, PartitionFilter partitionFilter) { + if (policy == null) { + policy = queryPolicyDefault; + } + + // @Ashish taskId will be zero by default here. + RecordSequenceRecordSet recordSet = new RecordSequenceRecordSet(statement.getTaskId(), policy.recordQueueSize); + queryPartitions(null, recordSet, policy, statement, partitionFilter); + return recordSet; + } + + /** + * Asynchronously execute query for specified partitions. + *

+ * Each record result is returned in separate onRecord() calls. + * + * @param eventLoop ignored, pass in null + * @param listener where to send results + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @param partitionFilter filter on a subset of data partitions + * @throws AerospikeException if query fails + */ + @Override + public void queryPartitions( + EventLoop eventLoop, + RecordSequenceListener listener, + QueryPolicy policy, + Statement statement, + PartitionFilter partitionFilter + ) { + if (policy == null) { + policy = queryPolicyDefault; + } + + long taskId = statement.prepareTaskId(); + PartitionTracker tracker = new PartitionTracker(policy, statement, 1, partitionFilter); + QueryCommandProxy command = new QueryCommandProxy(executor, listener, policy, + statement, taskId, partitionFilter, tracker); + command.execute(); + } + + /** + * Execute query, apply statement's aggregation function, and return result iterator. The query + * executor puts results on a queue in separate threads. The calling thread concurrently pops + * results off the queue through the result iterator. + *

+ * The aggregation function is called on both server and client (final reduce). Therefore, + * the Lua script files must also reside on both server and client. + * The package name is used to locate the udf file location: + *

+ * {@code udf file = /.lua} + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @param packageName server package where user defined function resides + * @param functionName aggregation function name + * @param functionArgs arguments to pass to function name, if any + * @return result iterator + * @throws AerospikeException if query fails + */ + @Override + public ResultSet queryAggregate( + QueryPolicy policy, + Statement statement, + String packageName, + String functionName, + Value... functionArgs + ) { + statement.setAggregateFunction(packageName, functionName, functionArgs); + return queryAggregate(policy, statement); + } + + /** + * Execute query, apply statement's aggregation function, and return result iterator. + * The aggregation function should be initialized via the statement's setAggregateFunction() + * and should be located in a resource or a filesystem file. + *

+ * The query executor puts results on a queue in separate threads. The calling thread + * concurrently pops results off the queue through the ResultSet iterator. + * The aggregation function is called on both server and client (final reduce). + * Therefore, the Lua script file must also reside on both server and client. + * + * @param policy query configuration parameters, pass in null for defaults + * @param statement query definition + * @throws AerospikeException if query fails + */ + @Override + public ResultSet queryAggregate(QueryPolicy policy, Statement statement) { + if (policy == null) { + policy = queryPolicyDefault; + } + + long taskId = statement.prepareTaskId(); + QueryAggregateCommandProxy commandProxy = new QueryAggregateCommandProxy( + executor, threadPool, policy, statement, taskId); + commandProxy.execute(); + return commandProxy.getResultSet(); + } + + /** + * Not supported in proxy client. + */ + @Override + public ResultSet queryAggregateNode(QueryPolicy policy, Statement statement, Node node) { + throw new AerospikeException(NotSupported + "queryAggregateNode"); + } + + //-------------------------------------------------------- + // Secondary Index functions + //-------------------------------------------------------- + + /** + * Not supported in proxy client. + */ + @Override + public IndexTask createIndex( + Policy policy, + String namespace, + String setName, + String indexName, + String binName, + IndexType indexType + ) { + throw new AerospikeException(NotSupported + "createIndex"); + } + + /** + * Not supported in proxy client. + */ + @Override + public IndexTask createIndex( + Policy policy, + String namespace, + String setName, + String indexName, + String binName, + IndexType indexType, + IndexCollectionType indexCollectionType, + CTX... ctx + ) { + throw new AerospikeException(NotSupported + "createIndex"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void createIndex( + EventLoop eventLoop, + IndexListener listener, + Policy policy, + String namespace, + String setName, + String indexName, + String binName, + IndexType indexType, + IndexCollectionType indexCollectionType, + CTX... ctx + ) { + throw new AerospikeException(NotSupported + "createIndex"); + } + + /** + * Not supported in proxy client. + */ + @Override + public IndexTask dropIndex(Policy policy, String namespace, String setName, String indexName) { + throw new AerospikeException(NotSupported + "dropIndex"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void dropIndex( + EventLoop eventLoop, + IndexListener listener, + Policy policy, + String namespace, + String setName, + String indexName + ) { + throw new AerospikeException(NotSupported + "dropIndex"); + } + + //----------------------------------------------------------------- + // Async Info functions (sync info functions located in Info class) + //----------------------------------------------------------------- + + /** + * Not supported in proxy client. + */ + @Override + public void info(EventLoop eventLoop, InfoListener listener, InfoPolicy policy, Node node, String... commands) { + throw new AerospikeException(NotSupported + "info"); + } + + //----------------------------------------------------------------- + // XDR - Cross datacenter replication + //----------------------------------------------------------------- + + /** + * Not supported in proxy client. + */ + @Override + public void setXDRFilter(InfoPolicy policy, String datacenter, String namespace, Expression filter) { + throw new AerospikeException(NotSupported + "setXDRFilter"); + } + + //------------------------------------------------------- + // User administration + //------------------------------------------------------- + + /** + * Not supported in proxy client. + */ + @Override + public void createUser(AdminPolicy policy, String user, String password, List roles) { + throw new AerospikeException(NotSupported + "createUser"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void dropUser(AdminPolicy policy, String user) { + throw new AerospikeException(NotSupported + "dropUser"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void changePassword(AdminPolicy policy, String user, String password) { + throw new AerospikeException(NotSupported + "changePassword"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void grantRoles(AdminPolicy policy, String user, List roles) { + throw new AerospikeException(NotSupported + "grantRoles"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void revokeRoles(AdminPolicy policy, String user, List roles) { + throw new AerospikeException(NotSupported + "revokeRoles"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void createRole(AdminPolicy policy, String roleName, List privileges) { + throw new AerospikeException(NotSupported + "createRole"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void createRole(AdminPolicy policy, String roleName, List privileges, List whitelist) { + throw new AerospikeException(NotSupported + "createRole"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void createRole( + AdminPolicy policy, + String roleName, + List privileges, + List whitelist, + int readQuota, + int writeQuota + ) { + throw new AerospikeException(NotSupported + "createRole"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void dropRole(AdminPolicy policy, String roleName) { + throw new AerospikeException(NotSupported + "dropRole"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void grantPrivileges(AdminPolicy policy, String roleName, List privileges) { + throw new AerospikeException(NotSupported + "grantPrivileges"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void revokePrivileges(AdminPolicy policy, String roleName, List privileges) { + throw new AerospikeException(NotSupported + "revokePrivileges"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void setWhitelist(AdminPolicy policy, String roleName, List whitelist) { + throw new AerospikeException(NotSupported + "setWhitelist"); + } + + /** + * Not supported in proxy client. + */ + @Override + public void setQuotas(AdminPolicy policy, String roleName, int readQuota, int writeQuota) { + throw new AerospikeException(NotSupported + "setQuotas"); + } + + /** + * Not supported in proxy client. + */ + @Override + public User queryUser(AdminPolicy policy, String user) { + throw new AerospikeException(NotSupported + "queryUser"); + } + + /** + * Not supported in proxy client. + */ + @Override + public List queryUsers(AdminPolicy policy) { + throw new AerospikeException(NotSupported + "queryUsers"); + } + + /** + * Not supported in proxy client. + */ + @Override + public Role queryRole(AdminPolicy policy, String roleName) { + throw new AerospikeException(NotSupported + "queryRole"); + } + + /** + * Not supported in proxy client. + */ + @Override + public List queryRoles(AdminPolicy policy) { + throw new AerospikeException(NotSupported + "queryRoles"); + } + + //------------------------------------------------------- + // Internal Methods + //------------------------------------------------------- + + private static GrpcClientPolicy toGrpcClientPolicy(ClientPolicy policy) { + List eventLoops = null; + Class channelType = null; + + if (policy.eventLoops != null) { + if (! (policy.eventLoops instanceof NettyEventLoops)) { + throw new AerospikeException(ResultCode.PARAMETER_ERROR, + "Netty event loops are required in proxy client"); + } + + NettyEventLoops nettyLoops = (NettyEventLoops)policy.eventLoops; + NettyEventLoop[] array = nettyLoops.getArray(); + eventLoops = new ArrayList<>(array.length); + + for (NettyEventLoop loop : array) { + eventLoops.add(loop.get()); + } + + channelType = nettyLoops.getSocketChannelClass(); + } + + int maxConnections = Math.min(MAX_CONNECTIONS, Math.max(MIN_CONNECTIONS, + Math.max(policy.asyncMaxConnsPerNode, policy.maxConnsPerNode))); + + return GrpcClientPolicy.newBuilder(eventLoops, channelType) + .maxChannels(maxConnections) + .connectTimeoutMillis(policy.timeout) + .closeTimeout(policy.closeTimeout) + .tlsPolicy(policy.tlsPolicy) + .build(); + } + + private ExecuteTask executeBackgroundTask(WritePolicy policy, Statement statement) { + if (policy == null) { + policy = writePolicyDefault; + } + + CompletableFuture future = new CompletableFuture<>(); + long taskId = statement.prepareTaskId(); + + BackgroundExecuteCommandProxy command = new BackgroundExecuteCommandProxy(executor, policy, + statement, taskId, future); + command.execute(); + + // Check whether the background task started. + getFuture(future); + + return new ExecuteTaskProxy(executor, taskId, statement.isScan()); + } + + private static WriteListener prepareWriteListener(final CompletableFuture future) { + return new WriteListener() { + @Override + public void onSuccess(Key key) { + future.complete(null); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static DeleteListener prepareDeleteListener(final CompletableFuture future) { + return new DeleteListener() { + @Override + public void onSuccess(Key key, boolean existed) { + future.complete(existed); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static RecordListener prepareRecordListener(final CompletableFuture future) { + return new RecordListener() { + @Override + public void onSuccess(Key key, Record record) { + future.complete(record); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static ExistsListener prepareExistsListener(final CompletableFuture future) { + return new ExistsListener() { + @Override + public void onSuccess(Key key, boolean exists) { + future.complete(exists); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static ExecuteListener prepareExecuteListener(final CompletableFuture future) { + return new ExecuteListener() { + @Override + public void onSuccess(Key key, Object obj) { + future.complete(obj); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static ExistsArrayListener prepareExistsArrayListener(final CompletableFuture future) { + return new ExistsArrayListener() { + @Override + public void onSuccess(Key[] keys, boolean[] exists) { + future.complete(exists); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static RecordArrayListener prepareRecordArrayListener(final CompletableFuture future) { + return new RecordArrayListener() { + @Override + public void onSuccess(Key[] keys, Record[] records) { + future.complete(records); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static BatchListListenerSync prepareBatchListListenerSync(final CompletableFuture future) { + return new BatchListListenerSync() { + @Override + public void onSuccess(List records, boolean status) { + future.complete(status); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static BatchOperateListListener prepareBatchOperateListListener(final CompletableFuture future) { + return new BatchOperateListListener() { + @Override + public void onSuccess(List records, boolean status) { + future.complete(status); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + }; + } + + private static BatchRecordArrayListener prepareBatchRecordArrayListener(final CompletableFuture future) { + return new BatchRecordArrayListener() { + @Override + public void onSuccess(BatchRecord[] records, boolean status) { + future.complete(new BatchResults(records, status)); + } + + @Override + public void onFailure(BatchRecord[] records, AerospikeException ae) { + future.completeExceptionally(new AerospikeException.BatchRecordArray(records, ae)); + } + }; + } + + static T getFuture(final CompletableFuture future) { + try { + return future.get(); + } + catch (ExecutionException e) { + if (e.getCause() instanceof AerospikeException) { + throw (AerospikeException)e.getCause(); + } + throw new AerospikeException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new AerospikeException(e); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/BackgroundExecuteCommandProxy.java b/proxy/src/com/aerospike/client/proxy/BackgroundExecuteCommandProxy.java new file mode 100644 index 000000000..f8ae22bc9 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/BackgroundExecuteCommandProxy.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.CompletableFuture; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.client.query.Statement; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.QueryGrpc; + +/** + * Implements asynchronous background query execute for the proxy. + */ +public class BackgroundExecuteCommandProxy extends MultiCommandProxy { + private final Statement statement; + private final long taskId; + private final CompletableFuture future; + + public BackgroundExecuteCommandProxy( + GrpcCallExecutor executor, + WritePolicy writePolicy, + Statement statement, + long taskId, + CompletableFuture future + ) { + super(QueryGrpc.getBackgroundExecuteStreamingMethod(), executor, writePolicy); + this.statement = statement; + this.taskId = taskId; + this.future = future; + } + + @Override + void writeCommand(Command command) { + // Nothing to do since there is no Aerospike payload. + } + + @Override + void parseResult(Parser parser) { + RecordProxy recordProxy = parseRecordResult(parser, false, true, false); + + // Only on response is expected. + if (recordProxy.resultCode != ResultCode.OK) { + throw new AerospikeException(recordProxy.resultCode); + } + + future.complete(null); + } + + @Override + void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + + @Override + protected Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + // Set the query parameters in the Aerospike request payload. + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + Kvs.BackgroundExecuteRequest.Builder queryRequestBuilder = + Kvs.BackgroundExecuteRequest.newBuilder(); + + queryRequestBuilder.setWritePolicy(GrpcConversions.toGrpc((WritePolicy)policy)); + queryRequestBuilder.setStatement(GrpcConversions.toGrpc(statement, taskId, 0)); + builder.setBackgroundExecuteRequest(queryRequestBuilder.build()); + + return builder; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/BatchProxy.java b/proxy/src/com/aerospike/client/proxy/BatchProxy.java new file mode 100644 index 000000000..4ae5a02f5 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/BatchProxy.java @@ -0,0 +1,956 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.List; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.BatchRead; +import com.aerospike.client.BatchRecord; +import com.aerospike.client.Key; +import com.aerospike.client.Operation; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.BatchAttr; +import com.aerospike.client.command.Command; +import com.aerospike.client.command.Command.KeyIter; +import com.aerospike.client.listener.BatchListListener; +import com.aerospike.client.listener.BatchOperateListListener; +import com.aerospike.client.listener.BatchRecordArrayListener; +import com.aerospike.client.listener.BatchRecordSequenceListener; +import com.aerospike.client.listener.BatchSequenceListener; +import com.aerospike.client.listener.ExistsArrayListener; +import com.aerospike.client.listener.ExistsSequenceListener; +import com.aerospike.client.listener.RecordArrayListener; +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.policy.BatchPolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; +import com.aerospike.proxy.client.Kvs; + +/** + * Batch proxy commands. + */ +public class BatchProxy { + //------------------------------------------------------- + // Batch Read Record List + //------------------------------------------------------- + + public interface BatchListListenerSync { + public void onSuccess(List records, boolean status); + public void onFailure(AerospikeException ae); + } + + public static final class ReadListCommandSync extends BaseCommand { + private final BatchListListenerSync listener; + private final List records; + private boolean status; + + public ReadListCommandSync( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchListListenerSync listener, + List records + ) { + super(executor, batchPolicy, true, records.size()); + this.listener = listener; + this.records = records; + this.status = true; + } + + @Override + void writeCommand(Command command) { + BatchRecordIterProxy iter = new BatchRecordIterProxy(records); + command.setBatchOperate(batchPolicy, iter); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRead record = records.get(parser.batchIndex); + + if (resultCode == 0) { + record.setRecord(parseRecord(parser)); + } + else { + record.setError(resultCode, false); + status = false; + } + } + + @Override + void onSuccess() { + listener.onSuccess(records, status); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + public static final class ReadListCommand extends BaseCommand { + private final BatchListListener listener; + private final List records; + + public ReadListCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchListListener listener, + List records + ) { + super(executor, batchPolicy, true, records.size()); + this.listener = listener; + this.records = records; + } + + @Override + void writeCommand(Command command) { + BatchRecordIterProxy iter = new BatchRecordIterProxy(records); + command.setBatchOperate(batchPolicy, iter); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRead record = records.get(parser.batchIndex); + + if (resultCode == 0) { + record.setRecord(parseRecord(parser)); + } + else { + record.setError(resultCode, false); + } + } + + @Override + void onSuccess() { + listener.onSuccess(records); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + public static final class ReadSequenceCommand extends BaseCommand { + private final BatchSequenceListener listener; + private final List records; + + public ReadSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchSequenceListener listener, + List records + ) { + super(executor, batchPolicy, true, records.size()); + this.listener = listener; + this.records = records; + } + + @Override + void writeCommand(Command command) { + BatchRecordIterProxy iter = new BatchRecordIterProxy(records); + command.setBatchOperate(batchPolicy, iter); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRead record = records.get(parser.batchIndex); + + if (resultCode == ResultCode.OK) { + record.setRecord(parseRecord(parser)); + } + else { + record.setError(resultCode, false); + } + listener.onRecord(record); + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch Read Key Array + //------------------------------------------------------- + + public static final class GetArrayCommand extends BaseCommand { + private final RecordArrayListener listener; + private final Record[] records; + private final Key[] keys; + private final String[] binNames; + private final Operation[] ops; + private final int readAttr; + + public GetArrayCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + RecordArrayListener listener, + Key[] keys, + String[] binNames, + Operation[] ops, + int readAttr, + boolean isOperation + ) { + super(executor, batchPolicy, isOperation, keys.length); + this.listener = listener; + this.keys = keys; + this.binNames = binNames; + this.ops = ops; + this.readAttr = readAttr; + this.records = new Record[keys.length]; + } + + @Override + void writeCommand(Command command) { + BatchAttr attr = new BatchAttr(batchPolicy, readAttr, ops); + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, binNames, ops, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + if (resultCode == ResultCode.OK) { + records[parser.batchIndex] = parseRecord(parser); + } + } + + @Override + void onSuccess() { + listener.onSuccess(keys, records); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(new AerospikeException.BatchRecords(records, ae)); + } + } + + public static final class GetSequenceCommand extends BaseCommand { + private final RecordSequenceListener listener; + private final Key[] keys; + private final String[] binNames; + private final Operation[] ops; + private final int readAttr; + + public GetSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + RecordSequenceListener listener, + Key[] keys, + String[] binNames, + Operation[] ops, + int readAttr, + boolean isOperation + ) { + super(executor, batchPolicy, isOperation, keys.length); + this.listener = listener; + this.keys = keys; + this.binNames = binNames; + this.ops = ops; + this.readAttr = readAttr; + } + + @Override + void writeCommand(Command command) { + BatchAttr attr = new BatchAttr(batchPolicy, readAttr, ops); + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, binNames, ops, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + Key keyOrig = keys[parser.batchIndex]; + + if (resultCode == ResultCode.OK) { + Record record = parseRecord(parser); + listener.onRecord(keyOrig, record); + } + else { + listener.onRecord(keyOrig, null); + } + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch Exists + //------------------------------------------------------- + + public static final class ExistsArrayCommand extends BaseCommand { + private final ExistsArrayListener listener; + private final Key[] keys; + private final boolean[] existsArray; + + public ExistsArrayCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + ExistsArrayListener listener, + Key[] keys + ) { + super(executor, batchPolicy, false, keys.length); + this.listener = listener; + this.keys = keys; + this.existsArray = new boolean[keys.length]; + } + + @Override + void writeCommand(Command command) { + BatchAttr attr = new BatchAttr(batchPolicy, Command.INFO1_READ | Command.INFO1_NOBINDATA); + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, null, null, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + if (parser.opCount > 0) { + throw new AerospikeException.Parse("Received bins that were not requested!"); + } + existsArray[parser.batchIndex] = resultCode == 0; + } + + @Override + void onSuccess() { + listener.onSuccess(keys, existsArray); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + public static final class ExistsSequenceCommand extends BaseCommand { + private final ExistsSequenceListener listener; + private final Key[] keys; + + public ExistsSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + ExistsSequenceListener listener, + Key[] keys + ) { + super(executor, batchPolicy, false, keys.length); + this.listener = listener; + this.keys = keys; + } + + @Override + void writeCommand(Command command) { + BatchAttr attr = new BatchAttr(batchPolicy, Command.INFO1_READ | Command.INFO1_NOBINDATA); + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, null, null, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + if (parser.opCount > 0) { + throw new AerospikeException.Parse("Received bins that were not requested!"); + } + listener.onExists(keys[parser.batchIndex], resultCode == 0); + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch Operate Record List + //------------------------------------------------------- + + public static final class OperateListCommand extends BaseCommand { + private final BatchOperateListListener listener; + private final List records; + private boolean status; + + public OperateListCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchOperateListListener listener, + List records + ) { + super(executor, batchPolicy, true, records.size()); + this.listener = listener; + this.records = records; + this.status = true; + } + + @Override + void writeCommand(Command command) { + BatchRecordIterProxy iter = new BatchRecordIterProxy(records); + command.setBatchOperate(batchPolicy, iter); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRecord record = records.get(parser.batchIndex); + + if (resultCode == ResultCode.OK) { + record.setRecord(parseRecord(parser)); + return; + } + + if (resultCode == ResultCode.UDF_BAD_RESPONSE) { + Record r = parseRecord(parser); + String m = r.getString("FAILURE"); + + if (m != null) { + // Need to store record because failure bin contains an error message. + record.record = r; + record.resultCode = resultCode; + record.inDoubt = inDoubt; + status = false; + return; + } + } + + record.setError(resultCode, inDoubt); + status = false; + } + + @Override + void onSuccess() { + listener.onSuccess(records, status); + } + + @Override + void onFailure(AerospikeException ae) { + if (ae.getInDoubt()) { + for (BatchRecord record : records) { + if (record.resultCode == ResultCode.NO_RESPONSE) { + record.inDoubt = record.hasWrite; + } + } + } + listener.onFailure(ae); + } + } + + public static final class OperateSequenceCommand extends BaseCommand { + private final BatchRecordSequenceListener listener; + private final List records; + + public OperateSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchRecordSequenceListener listener, + List records + ) { + super(executor, batchPolicy, true, records.size()); + this.listener = listener; + this.records = records; + } + + @Override + void writeCommand(Command command) { + BatchRecordIterProxy iter = new BatchRecordIterProxy(records); + command.setBatchOperate(batchPolicy, iter); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRecord record = records.get(parser.batchIndex); + + if (resultCode == ResultCode.OK) { + record.setRecord(parseRecord(parser)); + } + else if (resultCode == ResultCode.UDF_BAD_RESPONSE) { + Record r = parseRecord(parser); + String m = r.getString("FAILURE"); + + if (m != null) { + // Need to store record because failure bin contains an error message. + record.record = r; + record.resultCode = resultCode; + record.inDoubt = inDoubt; + } + else { + record.setError(resultCode, inDoubt); + } + } + else { + record.setError(resultCode, inDoubt); + } + + listener.onRecord(record, parser.batchIndex); + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + if (ae.getInDoubt()) { + for (BatchRecord record : records) { + if (record.resultCode == ResultCode.NO_RESPONSE) { + record.inDoubt = record.hasWrite; + } + } + } + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch Operate Key Array + //------------------------------------------------------- + + public static final class OperateRecordArrayCommand extends BaseCommand { + private final BatchRecordArrayListener listener; + private final BatchRecord[] records; + private final Key[] keys; + private final Operation[] ops; + private final BatchAttr attr; + private boolean status; + + public OperateRecordArrayCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + Key[] keys, + Operation[] ops, + BatchRecordArrayListener listener, + BatchAttr attr + ) { + super(executor, batchPolicy, ops != null, keys.length); + this.keys = keys; + this.ops = ops; + this.listener = listener; + this.attr = attr; + this.status = true; + this.records = new BatchRecord[keys.length]; + + for (int i = 0; i < keys.length; i++) { + this.records[i] = new BatchRecord(keys[i], attr.hasWrite); + } + } + + @Override + void writeCommand(Command command) { + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, null, ops, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRecord record = records[parser.batchIndex]; + + if (resultCode == 0) { + record.setRecord(parseRecord(parser)); + } + else { + record.setError(resultCode, inDoubt); + status = false; + } + } + + @Override + void onSuccess() { + listener.onSuccess(records, status); + } + + @Override + void onFailure(AerospikeException ae) { + if (ae.getInDoubt()) { + for (BatchRecord record : records) { + if (record.resultCode == ResultCode.NO_RESPONSE) { + record.inDoubt = record.hasWrite; + } + } + } + listener.onFailure(records, ae); + } + } + + public static final class OperateRecordSequenceCommand extends BaseCommand { + private final BatchRecordSequenceListener listener; + private final Key[] keys; + private final Operation[] ops; + private final BatchAttr attr; + + public OperateRecordSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + Key[] keys, + Operation[] ops, + BatchRecordSequenceListener listener, + BatchAttr attr + ) { + super(executor, batchPolicy, ops != null, keys.length); + this.keys = keys; + this.ops = ops; + this.listener = listener; + this.attr = attr; + } + + @Override + void writeCommand(Command command) { + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchOperate(batchPolicy, iter, null, ops, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + Key keyOrig = keys[parser.batchIndex]; + BatchRecord record; + + if (resultCode == ResultCode.OK) { + record = new BatchRecord(keyOrig, parseRecord(parser), attr.hasWrite); + } + else { + record = new BatchRecord(keyOrig, null, resultCode, inDoubt, attr.hasWrite); + } + + listener.onRecord(record, parser.batchIndex); + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch UDF + //------------------------------------------------------- + + public static final class UDFArrayCommand extends BaseCommand { + private final BatchRecordArrayListener listener; + private final BatchRecord[] records; + private final Key[] keys; + private final String packageName; + private final String functionName; + private final byte[] argBytes; + private final BatchAttr attr; + private boolean status; + + public UDFArrayCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchRecordArrayListener listener, + Key[] keys, + String packageName, + String functionName, + byte[] argBytes, + BatchAttr attr + ) { + super(executor, batchPolicy, false, keys.length); + this.listener = listener; + this.keys = keys; + this.packageName = packageName; + this.functionName = functionName; + this.argBytes = argBytes; + this.attr = attr; + this.status = true; + + this.records = new BatchRecord[keys.length]; + + for (int i = 0; i < keys.length; i++) { + this.records[i] = new BatchRecord(keys[i], attr.hasWrite); + } + } + + @Override + void writeCommand(Command command) { + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchUDF(batchPolicy, iter, packageName, functionName, argBytes, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + BatchRecord record = records[parser.batchIndex]; + + if (resultCode == ResultCode.OK) { + record.setRecord(parseRecord(parser)); + return; + } + + if (resultCode == ResultCode.UDF_BAD_RESPONSE) { + Record r = parseRecord(parser); + String m = r.getString("FAILURE"); + + if (m != null) { + // Need to store record because failure bin contains an error message. + record.record = r; + record.resultCode = resultCode; + record.inDoubt = inDoubt; + status = false; + return; + } + } + + record.setError(resultCode, inDoubt); + status = false; + } + + @Override + void onSuccess() { + listener.onSuccess(records, status); + } + + @Override + void onFailure(AerospikeException ae) { + if (ae.getInDoubt()) { + for (BatchRecord record : records) { + if (record.resultCode == ResultCode.NO_RESPONSE) { + record.inDoubt = record.hasWrite; + } + } + } + listener.onFailure(records, ae); + } + } + + public static final class UDFSequenceCommand extends BaseCommand { + private final BatchRecordSequenceListener listener; + private final Key[] keys; + private final String packageName; + private final String functionName; + private final byte[] argBytes; + private final BatchAttr attr; + + public UDFSequenceCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + BatchRecordSequenceListener listener, + Key[] keys, + String packageName, + String functionName, + byte[] argBytes, + BatchAttr attr + ) { + super(executor, batchPolicy, false, keys.length); + this.listener = listener; + this.keys = keys; + this.packageName = packageName; + this.functionName = functionName; + this.argBytes = argBytes; + this.attr = attr; + } + + @Override + void writeCommand(Command command) { + KeyIterProxy iter = new KeyIterProxy(keys); + command.setBatchUDF(batchPolicy, iter, packageName, functionName, argBytes, attr); + } + + @Override + void parse(Parser parser, int resultCode) { + Key keyOrig = keys[parser.batchIndex]; + BatchRecord record; + + if (resultCode == ResultCode.OK) { + record = new BatchRecord(keyOrig, parseRecord(parser), attr.hasWrite); + } + else if (resultCode == ResultCode.UDF_BAD_RESPONSE) { + Record r = parseRecord(parser); + String m = r.getString("FAILURE"); + + if (m != null) { + // Need to store record because failure bin contains an error message. + record = new BatchRecord(keyOrig, r, resultCode, inDoubt, attr.hasWrite); + } + else { + record = new BatchRecord(keyOrig, null, resultCode, inDoubt, attr.hasWrite); + } + } + else { + record = new BatchRecord(keyOrig, null, resultCode, inDoubt, attr.hasWrite); + } + listener.onRecord(record, parser.batchIndex); + } + + @Override + void onSuccess() { + listener.onSuccess(); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } + } + + //------------------------------------------------------- + // Batch Base + //------------------------------------------------------- + + private static abstract class BaseCommand extends CommandProxy { + final BatchPolicy batchPolicy; + final boolean isOperation; + + public BaseCommand( + GrpcCallExecutor executor, + BatchPolicy batchPolicy, + boolean isOperation, + int numExpectedResponses + ) { + super(KVSGrpc.getBatchOperateStreamingMethod(), executor, batchPolicy, numExpectedResponses); + this.batchPolicy = batchPolicy; + this.isOperation = isOperation; + } + + @Override + final void onResponse(Kvs.AerospikeResponsePayload response) { + // Check final response status for client errors (negative error codes). + int resultCode = response.getStatus(); + boolean hasNext = response.getHasNext(); + + if (resultCode != 0 && !hasNext) { + notifyFailure(new AerospikeException(resultCode)); + return; + } + + // Server errors are checked in response payload in Parser. + byte[] bytes = response.getPayload().toByteArray(); + Parser parser = new Parser(bytes); + parser.parseProto(); + int rc = parser.parseHeader(); + + if (hasNext) { + if (resultCode == 0) { + resultCode = rc; + } + parser.skipKey(); + parse(parser, resultCode); + return; + } + + if (rc == ResultCode.OK) { + try { + onSuccess(); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + else { + notifyFailure(new AerospikeException(rc)); + } + } + + final Record parseRecord(Parser parser) { + return parser.parseRecord(isOperation); + } + + abstract void parse(Parser parser, int resultCode); + abstract void onSuccess(); + } + + //------------------------------------------------------- + // Proxy Iterators + //------------------------------------------------------- + + private static class BatchRecordIterProxy implements KeyIter { + private final List records; + private final int size; + private int offset; + private int index; + + public BatchRecordIterProxy(List records) { + this.records = records; + this.size = records.size(); + } + + @Override + public int size() { + return size; + } + + @Override + public BatchRecord next() { + if (index >= size) { + return null; + } + offset = index++; + return records.get(offset); + } + + @Override + public int offset() { + return offset; + } + + @Override + public void reset() { + index = 0; + } + } + + private static class KeyIterProxy implements KeyIter { + private final Key[] keys; + private int offset; + private int index; + + public KeyIterProxy(Key[] keys) { + this.keys = keys; + } + + @Override + public int size() { + return keys.length; + } + + @Override + public Key next() { + if (index >= keys.length) { + return null; + } + offset = index++; + return keys[offset]; + } + + @Override + public int offset() { + return offset; + } + + @Override + public void reset() { + index = 0; + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/CommandProxy.java b/proxy/src/com/aerospike/client/proxy/CommandProxy.java new file mode 100644 index 000000000..c89eacab0 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/CommandProxy.java @@ -0,0 +1,202 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.TimeUnit; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.client.proxy.grpc.GrpcStreamingCall; +import com.aerospike.client.util.Util; +import com.aerospike.proxy.client.Kvs; +import com.google.protobuf.ByteString; + +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; + +public abstract class CommandProxy { + final Policy policy; + private final GrpcCallExecutor executor; + private final MethodDescriptor methodDescriptor; + private long deadlineNanos; + private int sendTimeoutMillis; + private int iteration = 1; + private final int numExpectedResponses; + boolean inDoubt; + + public CommandProxy( + MethodDescriptor methodDescriptor, + GrpcCallExecutor executor, + Policy policy, + int numExpectedResponses + ) { + this.methodDescriptor = methodDescriptor; + this.executor = executor; + this.policy = policy; + this.numExpectedResponses = numExpectedResponses; + } + + final void execute() { + if (policy.totalTimeout > 0) { + deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(policy.totalTimeout); + sendTimeoutMillis = (policy.socketTimeout > 0 && policy.socketTimeout < policy.totalTimeout)? + policy.socketTimeout : policy.totalTimeout; + } + else { + deadlineNanos = 0; // No total deadline. + sendTimeoutMillis = Math.max(policy.socketTimeout, 0); + } + + executeCommand(); + } + + private void executeCommand() { + long sendDeadlineNanos = + (sendTimeoutMillis > 0) ? + System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(sendTimeoutMillis) : 0; + + Kvs.AerospikeRequestPayload.Builder builder = getRequestBuilder(); + + executor.execute(new GrpcStreamingCall(methodDescriptor, builder, + policy, iteration, deadlineNanos, sendDeadlineNanos, numExpectedResponses, + new StreamObserver() { + @Override + public void onNext(Kvs.AerospikeResponsePayload response) { + try { + inDoubt |= response.getInDoubt(); + onResponse(response); + } + catch (Throwable t) { + onFailure(t); + // Re-throw to abort at the proxy/ + throw t; + } + } + + @Override + public void onError(Throwable t) { + inDoubt = true; + onFailure(t); + } + + @Override + public void onCompleted() { + } + })); + } + + boolean retry() { + if (iteration > policy.maxRetries) { + return false; + } + + if (policy.totalTimeout > 0) { + long remaining = deadlineNanos - System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(policy.sleepBetweenRetries); + + if (remaining <= 0) { + return false; + } + } + + iteration++; + executor.getEventLoop().schedule(this::retryNow, policy.sleepBetweenRetries, TimeUnit.MILLISECONDS); + return true; + } + + private void retryNow() { + try { + executeCommand(); + } + catch (AerospikeException ae) { + notifyFailure(ae); + } + catch (Throwable t) { + notifyFailure(new AerospikeException(ResultCode.CLIENT_ERROR, t)); + } + } + + private void onFailure(Throwable t) { + AerospikeException ae; + + try { + if (t instanceof AerospikeException) { + ae = (AerospikeException)t; + + if (ae.getResultCode() == ResultCode.TIMEOUT) { + ae = new AerospikeException.Timeout(policy, false); + } + } + else if (t instanceof StatusRuntimeException) { + StatusRuntimeException sre = (StatusRuntimeException)t; + Status.Code code = sre.getStatus().getCode(); + + if (code == Status.Code.UNAVAILABLE) { + if (retry()) { + return; + } + } + ae = GrpcConversions.toAerospike(sre, policy, iteration); + } + else { + ae = new AerospikeException(ResultCode.CLIENT_ERROR, t); + } + } + catch (AerospikeException ae2) { + ae = ae2; + } + catch (Throwable t2) { + ae = new AerospikeException(ResultCode.CLIENT_ERROR, t2); + } + + notifyFailure(ae); + } + + final void notifyFailure(AerospikeException ae) { + try { + ae.setPolicy(policy); + ae.setIteration(iteration); + ae.setInDoubt(inDoubt); + onFailure(ae); + } + catch (Throwable t) { + Log.error("onFailure() error: " + Util.getStackTrace(t)); + } + } + + static void logOnSuccessError(Throwable t) { + Log.error("onSuccess() error: " + Util.getStackTrace(t)); + } + + Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + Command command = new Command(policy.socketTimeout, policy.totalTimeout, policy.maxRetries); + writeCommand(command); + + ByteString payload = ByteString.copyFrom(command.dataBuffer, 0, command.dataOffset); + return Kvs.AerospikeRequestPayload.newBuilder().setPayload(payload); + } + + abstract void writeCommand(Command command); + abstract void onResponse(Kvs.AerospikeResponsePayload response); + abstract void onFailure(AerospikeException ae); +} diff --git a/proxy/src/com/aerospike/client/proxy/DeleteCommandProxy.java b/proxy/src/com/aerospike/client/proxy/DeleteCommandProxy.java new file mode 100644 index 000000000..bd0aefece --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/DeleteCommandProxy.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.DeleteListener; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class DeleteCommandProxy extends SingleCommandProxy { + private final DeleteListener listener; + private final WritePolicy writePolicy; + private final Key key; + + public DeleteCommandProxy( + GrpcCallExecutor executor, + DeleteListener listener, + WritePolicy writePolicy, + Key key + ) { + super(KVSGrpc.getDeleteStreamingMethod(), executor, writePolicy); + this.listener = listener; + this.writePolicy = writePolicy; + this.key = key; + } + + @Override + void writeCommand(Command command) { + command.setDelete(writePolicy, key); + } + + @Override + void parseResult(Parser parser) { + int resultCode = parser.parseResultCode(); + boolean existed; + + switch (resultCode) { + case ResultCode.OK: + existed = true; + break; + + case ResultCode.KEY_NOT_FOUND_ERROR: + existed = false; + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + existed = true; + break; + + default: + throw new AerospikeException(resultCode); + } + + try { + listener.onSuccess(key, existed); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ExecuteCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ExecuteCommandProxy.java new file mode 100644 index 000000000..4961a6c31 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ExecuteCommandProxy.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.Map; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.Value; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.ExecuteListener; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class ExecuteCommandProxy extends ReadCommandProxy { + private final ExecuteListener executeListener; + private final WritePolicy writePolicy; + private final Key key; + private final String packageName; + private final String functionName; + private final Value[] args; + + public ExecuteCommandProxy( + GrpcCallExecutor executor, + ExecuteListener executeListener, + WritePolicy writePolicy, + Key key, + String packageName, + String functionName, + Value[] args + ) { + super(KVSGrpc.getExecuteStreamingMethod(), executor, null, writePolicy, key, false); + this.executeListener = executeListener; + this.writePolicy = writePolicy; + this.key = key; + this.packageName = packageName; + this.functionName = functionName; + this.args = args; + } + + @Override + void writeCommand(Command command) { + command.setUdf(writePolicy, key, packageName, functionName, args); + } + + @Override + void parseResult(Parser parser) { + Record record = parseRecordResult(parser); + Object obj = parseEndResult(record); + + try { + executeListener.onSuccess(key, obj); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + private static Object parseEndResult(Record record) { + if (record == null || record.bins == null) { + return null; + } + + Map map = record.bins; + + Object obj = map.get("SUCCESS"); + + if (obj != null) { + return obj; + } + + // User defined functions don't have to return a value. + if (map.containsKey("SUCCESS")) { + return null; + } + + obj = map.get("FAILURE"); + + if (obj != null) { + throw new AerospikeException(obj.toString()); + } + throw new AerospikeException("Invalid UDF return value"); + } + + @Override + protected void handleNotFound(int resultCode) { + throw new AerospikeException(resultCode); + } + + @Override + void onFailure(AerospikeException ae) { + executeListener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ExecuteTaskProxy.java b/proxy/src/com/aerospike/client/proxy/ExecuteTaskProxy.java new file mode 100644 index 000000000..b81598f0f --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ExecuteTaskProxy.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.CompletableFuture; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.task.ExecuteTask; + +public class ExecuteTaskProxy extends ExecuteTask { + private final long taskId; + private final boolean scan; + private final GrpcCallExecutor callExecutor; + + /** + * Initialize task with fields needed to query server nodes. + */ + public ExecuteTaskProxy(GrpcCallExecutor executor, long taskId, boolean isScan) { + super(taskId, isScan); + this.callExecutor = executor; + this.taskId = taskId; + this.scan = isScan; + } + + /** + * Return task id. + */ + public long getTaskId() { + return taskId; + } + + /** + * Query all nodes for task completion status. + */ + @Override + public int queryStatus() throws AerospikeException { + CompletableFuture future = new CompletableFuture<>(); + ExecuteTaskStatusCommandProxy command = new ExecuteTaskStatusCommandProxy(callExecutor, + new WritePolicy(), taskId, scan, future); + command.execute(); + return AerospikeClientProxy.getFuture(future); + } +} \ No newline at end of file diff --git a/proxy/src/com/aerospike/client/proxy/ExecuteTaskStatusCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ExecuteTaskStatusCommandProxy.java new file mode 100644 index 000000000..5a0ed3251 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ExecuteTaskStatusCommandProxy.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.CompletableFuture; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.QueryGrpc; + +/** + * Fetch status for a background task. + */ +public class ExecuteTaskStatusCommandProxy extends MultiCommandProxy { + private final long taskId; + private final boolean isScan; + private final CompletableFuture future; + private int status; + + public ExecuteTaskStatusCommandProxy( + GrpcCallExecutor executor, + WritePolicy writePolicy, + long taskId, + boolean isScan, + CompletableFuture future + ) { + super(QueryGrpc.getBackgroundTaskStatusStreamingMethod(), executor, writePolicy); + this.taskId = taskId; + this.isScan = isScan; + this.future = future; + } + + @Override + protected void writeCommand(Command command) { + // Nothing to do since there is no Aerospike payload. + } + + @Override + protected void parseResult(Parser parser) { + RecordProxy recordProxy = parseRecordResult(parser, false, true, false); + + // Only on response is expected. + if (recordProxy.resultCode != ResultCode.OK) { + throw new AerospikeException(recordProxy.resultCode); + } + + // Status has been set in onResponse + future.complete(status); + } + + @Override + void onResponse(Kvs.AerospikeResponsePayload response) { + // Set the value but do not report the result until the resultcode + // is computed in parseResult + if (!response.hasField(response.getDescriptorForType().findFieldByName("backgroundTaskStatus"))) { + throw new AerospikeException.Parse("missing task status field"); + } + status = response.getBackgroundTaskStatusValue(); + super.onResponse(response); + } + + @Override + protected void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } + + @Override + protected Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + // Set the query parameters in the Aerospike request payload. + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + Kvs.BackgroundTaskStatusRequest.Builder statusRequestBuilder = Kvs.BackgroundTaskStatusRequest.newBuilder(); + statusRequestBuilder.setTaskId(taskId); + statusRequestBuilder.setIsScan(isScan); + + builder.setBackgroundTaskStatusRequest(statusRequestBuilder.build()); + return builder; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ExistsCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ExistsCommandProxy.java new file mode 100644 index 000000000..fa9ff8de9 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ExistsCommandProxy.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.ExistsListener; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class ExistsCommandProxy extends SingleCommandProxy { + private final ExistsListener listener; + private final Key key; + + public ExistsCommandProxy( + GrpcCallExecutor executor, + ExistsListener listener, + Policy policy, + Key key + ) { + super(KVSGrpc.getExistsStreamingMethod(), executor, policy); + this.listener = listener; + this.key = key; + } + + @Override + void writeCommand(Command command) { + command.setExists(policy, key); + } + + @Override + void parseResult(Parser parser) { + int resultCode = parser.parseResultCode(); + boolean exists; + + switch (resultCode) { + case ResultCode.OK: + exists = true; + break; + + case ResultCode.KEY_NOT_FOUND_ERROR: + exists = false; + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + exists = true; + break; + + default: + throw new AerospikeException(resultCode); + } + + try { + listener.onSuccess(key, exists); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/MultiCommandProxy.java b/proxy/src/com/aerospike/client/proxy/MultiCommandProxy.java new file mode 100644 index 000000000..4b637aedd --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/MultiCommandProxy.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.query.BVal; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.MethodDescriptor; + +public abstract class MultiCommandProxy extends CommandProxy { + boolean hasNext; + + public MultiCommandProxy( + MethodDescriptor methodDescriptor, + GrpcCallExecutor executor, + Policy policy + ) { + super(methodDescriptor, executor, policy, -1); + } + + @Override + void onResponse(Kvs.AerospikeResponsePayload response) { + // Check response status for client errors (negative error codes). + // Server errors are checked in response payload in Parser. + int status = response.getStatus(); + + if (status != 0) { + notifyFailure(new AerospikeException(status)); + return; + } + + hasNext = response.getHasNext(); + byte[] bytes = response.getPayload().toByteArray(); + Parser parser = new Parser(bytes); + parser.parseProto(); + parseResult(parser); + } + + final RecordProxy parseRecordResult( + Parser parser, + boolean isOperation, + boolean parseKey, + boolean parseBVal + ) { + Record record = null; + Key key = null; + BVal bVal = parseBVal ? new BVal() : null; + int resultCode = parser.parseHeader(); + + switch (resultCode) { + case ResultCode.OK: + if (parseKey) { + key = parser.parseKey(bVal); + } + else { + parser.skipKey(); + } + if (parser.opCount == 0) { + // Bin data was not returned. + record = new Record(null, parser.generation, parser.expiration); + } + else { + record = parser.parseRecord(isOperation); + } + break; + + case ResultCode.KEY_NOT_FOUND_ERROR: + handleNotFound(resultCode); + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + break; + + case ResultCode.UDF_BAD_RESPONSE: + parser.skipKey(); + record = parser.parseRecord(isOperation); + handleUdfError(record, resultCode); + break; + + default: + throw new AerospikeException(resultCode); + } + + return new RecordProxy(resultCode, key, record, bVal); + } + + protected void handleNotFound(int resultCode) { + // Do nothing in default case. Record will be null. + } + + protected void handleUdfError(Record record, int resultCode) { + String ret = (String)record.bins.get("FAILURE"); + + if (ret == null) { + throw new AerospikeException(resultCode); + } + + String message; + int code; + + try { + String[] list = ret.split(":"); + code = Integer.parseInt(list[2].trim()); + message = list[0] + ':' + list[1] + ' ' + list[3]; + } + catch (Exception e) { + // Use generic exception if parse error occurs. + throw new AerospikeException(resultCode, ret); + } + + throw new AerospikeException(code, message); + } + + abstract void parseResult(Parser parser); +} diff --git a/proxy/src/com/aerospike/client/proxy/OperateCommandProxy.java b/proxy/src/com/aerospike/client/proxy/OperateCommandProxy.java new file mode 100644 index 000000000..683eb15a0 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/OperateCommandProxy.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.command.Command; +import com.aerospike.client.command.OperateArgs; +import com.aerospike.client.listener.RecordListener; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class OperateCommandProxy extends ReadCommandProxy { + private final OperateArgs args; + + public OperateCommandProxy( + GrpcCallExecutor executor, + RecordListener listener, + Policy policy, + Key key, + OperateArgs args + ) { + super(KVSGrpc.getOperateStreamingMethod(), executor, listener, policy, key, true); + this.args = args; + } + + @Override + void writeCommand(Command command) { + command.setOperate(args.writePolicy, key, args); + } + + @Override + protected void handleNotFound(int resultCode) { + // Only throw not found exception for command with write operations. + // Read-only command operations return a null record. + if (args.hasWrite) { + throw new AerospikeException(resultCode); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/Parser.java b/proxy/src/com/aerospike/client/proxy/Parser.java new file mode 100644 index 000000000..940c6f82d --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/Parser.java @@ -0,0 +1,245 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import org.luaj.vm2.LuaValue; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.Value; +import com.aerospike.client.command.Buffer; +import com.aerospike.client.command.Command; +import com.aerospike.client.command.Command.OpResults; +import com.aerospike.client.command.FieldType; +import com.aerospike.client.lua.LuaInstance; +import com.aerospike.client.query.BVal; + +public final class Parser { + private byte[] buffer; + private int offset; + private int receiveSize; + private int resultCode; + int generation; + int expiration; + int batchIndex; + int fieldCount; + int opCount; + int info3; + + public Parser(byte[] buffer) { + this.buffer = buffer; + } + + public void parseProto() { + long sz = Buffer.bytesToLong(buffer, offset); + receiveSize = (int)(sz & 0xFFFFFFFFFFFFL); + int totalSize = receiveSize + 8; + + if (totalSize != buffer.length) { + throw new AerospikeException("size " + totalSize + " != buffer length " + buffer.length); + } + + offset += 8; + long type = (sz >> 48) & 0xff; + + if (type == Command.AS_MSG_TYPE) { + offset += 5; + } + else if (type == Command.MSG_TYPE_COMPRESSED) { + int usize = (int)Buffer.bytesToLong(buffer, offset); + offset += 8; + + byte[] buf = new byte[usize]; + + Inflater inf = new Inflater(); + try { + inf.setInput(buffer, offset, receiveSize - 8); + int rsize; + + try { + rsize = inf.inflate(buf); + } + catch (DataFormatException dfe) { + throw new AerospikeException.Serialize(dfe); + } + + if (rsize != usize) { + throw new AerospikeException("Decompressed size " + rsize + " is not expected " + usize); + } + + buffer = buf; + offset = 13; + } + finally { + inf.end(); + } + } + else { + throw new AerospikeException("Invalid proto type: " + type + " Expected: " + Command.AS_MSG_TYPE); + } + info3 = buffer[offset - 2] & 0xFF; + } + + public int parseResultCode() { + return buffer[offset] & 0xFF; + } + + public int parseHeader() { + resultCode = parseResultCode(); + offset += 1; + generation = Buffer.bytesToInt(buffer, offset); + offset += 4; + expiration = Buffer.bytesToInt(buffer, offset); + offset += 4; + batchIndex = Buffer.bytesToInt(buffer, offset); + offset += 4; + fieldCount = Buffer.bytesToShort(buffer, offset); + offset += 2; + opCount = Buffer.bytesToShort(buffer, offset); + offset += 2; + return resultCode; + } + + public void skipKey() { + // There can be fields in the response (setname etc). + // But for now, ignore them. Expose them to the API if needed in the future. + for (int i = 0; i < fieldCount; i++) { + int fieldlen = Buffer.bytesToInt(buffer, offset); + offset += 4 + fieldlen; + } + } + + public Key parseKey(BVal bVal) { + byte[] digest = null; + String namespace = null; + String setName = null; + Value userKey = null; + + for (int i = 0; i < fieldCount; i++) { + int fieldlen = Buffer.bytesToInt(buffer, offset); + offset += 4; + + int fieldtype = buffer[offset++]; + int size = fieldlen - 1; + + switch (fieldtype) { + case FieldType.DIGEST_RIPE: + digest = new byte[size]; + System.arraycopy(buffer, offset, digest, 0, size); + break; + + case FieldType.NAMESPACE: + namespace = Buffer.utf8ToString(buffer, offset, size); + break; + + case FieldType.TABLE: + setName = Buffer.utf8ToString(buffer, offset, size); + break; + + case FieldType.KEY: + int type = buffer[offset++]; + size--; + userKey = Buffer.bytesToKeyValue(type, buffer, offset, size); + break; + + case FieldType.BVAL_ARRAY: + bVal.val = Buffer.littleBytesToLong(buffer, offset); + break; + } + offset += size; + } + return new Key(namespace, digest, setName, userKey); + } + + public Record parseRecord(boolean isOperation) { + Map bins = new LinkedHashMap<>(); + + for (int i = 0; i < opCount; i++) { + int opSize = Buffer.bytesToInt(buffer, offset); + byte particleType = buffer[offset + 5]; + byte nameSize = buffer[offset + 7]; + String name = Buffer.utf8ToString(buffer, offset + 8, nameSize); + offset += 4 + 4 + nameSize; + + int particleBytesSize = opSize - (4 + nameSize); + Object value = Buffer.bytesToParticle(particleType, buffer, offset, particleBytesSize); + offset += particleBytesSize; + + if (isOperation) { + if (bins.containsKey(name)) { + // Multiple values returned for the same bin. + Object prev = bins.get(name); + + if (prev instanceof Command.OpResults) { + // List already exists. Add to it. + Command.OpResults list = (Command.OpResults)prev; + list.add(value); + } + else { + // Make a list to store all values. + Command.OpResults list = new OpResults(); + list.add(prev); + list.add(value); + bins.put(name, list); + } + } + else { + bins.put(name, value); + } + } + else { + bins.put(name, value); + } + } + return new Record(bins, generation, expiration); + } + + public LuaValue getLuaAggregateValue(LuaInstance instance) { + // Parse aggregateValue. + int opSize = Buffer.bytesToInt(buffer, offset); + offset += 5; + byte particleType = buffer[offset]; + offset += 2; + byte nameSize = buffer[offset++]; + + String name = Buffer.utf8ToString(buffer, offset, nameSize); + offset += nameSize; + + int particleBytesSize = opSize - (4 + nameSize); + + if (!name.equals("SUCCESS")) { + if (name.equals("FAILURE")) { + Object value = Buffer.bytesToParticle(particleType, buffer, offset, particleBytesSize); + throw new AerospikeException(ResultCode.QUERY_GENERIC, value != null ? value.toString() : null); + } + else { + throw new AerospikeException(ResultCode.PARSE_ERROR, "Query aggregate expected bin name SUCCESS. Received " + name); + } + } + + LuaValue aggregateValue = instance.getLuaValue(particleType, buffer, offset, particleBytesSize); + offset += particleBytesSize; + return aggregateValue; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/QueryAggregateCommandProxy.java b/proxy/src/com/aerospike/client/proxy/QueryAggregateCommandProxy.java new file mode 100644 index 000000000..9a4d8a1d2 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/QueryAggregateCommandProxy.java @@ -0,0 +1,250 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.luaj.vm2.LuaInteger; +import org.luaj.vm2.LuaValue; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.client.Value; +import com.aerospike.client.command.Command; +import com.aerospike.client.lua.LuaCache; +import com.aerospike.client.lua.LuaInputStream; +import com.aerospike.client.lua.LuaInstance; +import com.aerospike.client.lua.LuaOutputStream; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.client.query.ResultSet; +import com.aerospike.client.query.Statement; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.QueryGrpc; + +/** + * Query aggregation command for the proxy. + */ +public final class QueryAggregateCommandProxy extends MultiCommandProxy implements Runnable { + private final BlockingQueue inputQueue; + private final ResultSetProxy resultSet; + private final LuaInstance lua; + private final Statement statement; + private final AtomicBoolean done; + private final long taskId; + private volatile Exception exception; + + public QueryAggregateCommandProxy( + GrpcCallExecutor executor, + ExecutorService threadPool, + QueryPolicy queryPolicy, + Statement statement, + long taskId + ) { + super(QueryGrpc.getQueryStreamingMethod(), executor, queryPolicy); + this.statement = statement; + this.taskId = taskId; + this.inputQueue = new ArrayBlockingQueue<>(500); + this.resultSet = new ResultSetProxy(this, queryPolicy.recordQueueSize); + this.done = new AtomicBoolean(); + + // Work around luaj LuaInteger static initialization bug. + // Calling LuaInteger.valueOf(long) is required because LuaValue.valueOf() does not have + // a method that takes in a long parameter. The problem is directly calling + // LuaInteger.valueOf(long) results in a static initialization error. + // + // If LuaValue.valueOf() is called before any luaj calls, then the static initializer in + // LuaInteger will be initialized properly. + LuaValue.valueOf(0); + + // Retrieve lua instance from cache. + lua = LuaCache.getInstance(); + + try { + // Start Lua thread which reads from a queue, applies aggregate function and + // writes to a result set. + threadPool.execute(this); + } + catch (RuntimeException re) { + // Put the lua instance back if thread creation fails. + LuaCache.putInstance(lua); + throw re; + } + } + + @Override + void writeCommand(Command command) { + // Nothing to do since there is no Aerospike payload. + } + + @Override + void parseResult(Parser parser) { + int resultCode = parser.parseHeader(); + parser.skipKey(); + + if (resultCode != 0) { + // Aggregation scans (with null query filter) will return KEY_NOT_FOUND_ERROR + // when the set does not exist on the target node. + if (resultCode == ResultCode.KEY_NOT_FOUND_ERROR) { + // Non-fatal error. + return; + } + throw new AerospikeException(resultCode); + } + + if (! super.hasNext) { + sendCompleted(); + return; + } + + if (parser.opCount != 1) { + throw new AerospikeException("Query aggregate expected exactly " + + "one bin. Received " + parser.opCount); + } + + LuaValue aggregateValue = parser.getLuaAggregateValue(lua); + + if (done.get()) { + throw new AerospikeException.QueryTerminated(); + } + + if (aggregateValue != null) { + try { + inputQueue.put(aggregateValue); + } + catch (InterruptedException ie) { + // Ignore + } + } + } + + @Override + void onFailure(AerospikeException ae) { + stop(ae); + } + + @Override + Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + // Set the query parameters in the Aerospike request payload. + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + Kvs.QueryRequest.Builder queryRequestBuilder = + Kvs.QueryRequest.newBuilder(); + + queryRequestBuilder.setQueryPolicy(GrpcConversions.toGrpc((QueryPolicy)policy)); + queryRequestBuilder.setStatement(GrpcConversions.toGrpc(statement, taskId, 0)); + builder.setQueryRequest(queryRequestBuilder.build()); + return builder; + } + + public void stop(Exception cause) { + // There is no need to stop threads if all threads have already completed. + if (done.compareAndSet(false, true)) { + exception = cause; + sendCancel(); + } + } + + private void sendCompleted() { + // Send end command to lua thread. + // It's critical that the end put succeeds. + // Loop through all interrupts. + while (true) { + try { + inputQueue.put(LuaValue.NIL); + break; + } + catch (InterruptedException ie) { + if (Log.debugEnabled()) { + Log.debug("Lua input queue " + taskId + " put " + + "interrupted"); + } + } + } + } + + private void sendCancel() { + // Clear lua input queue to ensure cancel is accepted. + inputQueue.clear(); + resultSet.abort(); + + // Send end command to lua input queue. + // It's critical that the end offer succeeds. + while (!inputQueue.offer(LuaValue.NIL)) { + // Queue must be full. Remove one item to make room. + if (inputQueue.poll() == null) { + // Can't offer or poll. Nothing further can be done. + if (Log.debugEnabled()) { + Log.debug("Lua input queue " + taskId + " both " + + "offer and poll failed on abort"); + } + break; + } + } + } + + public void checkForException() { + // Throw an exception if an error occurred. + if (exception != null) { + if (exception instanceof AerospikeException) { + throw (AerospikeException)exception; + } + else { + throw new AerospikeException(exception); + } + } + } + + public void run() { + try { + lua.loadPackage(statement); + + LuaValue[] args = new LuaValue[4 + statement.getFunctionArgs().length]; + args[0] = lua.getFunction(statement.getFunctionName()); + args[1] = LuaInteger.valueOf(2); + args[2] = new LuaInputStream(inputQueue); + args[3] = new LuaOutputStream(resultSet); + int count = 4; + + for (Value value : statement.getFunctionArgs()) { + args[count++] = value.getLuaValue(lua); + } + lua.call("apply_stream", args); + } + catch (Exception e) { + stop(e); + } + finally { + // Send end command to user's result set. + // If query was already cancelled, this put will be ignored. + resultSet.put(ResultSet.END); + LuaCache.putInstance(lua); + } + } + + long getTaskId() { + return taskId; + } + + public ResultSet getResultSet() { + return resultSet; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/QueryCommandProxy.java b/proxy/src/com/aerospike/client/proxy/QueryCommandProxy.java new file mode 100644 index 000000000..a8739e2a0 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/QueryCommandProxy.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.client.query.PartitionFilter; +import com.aerospike.client.query.PartitionTracker; +import com.aerospike.client.query.Statement; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.QueryGrpc; + +/** + * Implements asynchronous query for the proxy. + */ +public class QueryCommandProxy extends ScanQueryBaseCommandProxy { + private final Statement statement; + private final PartitionFilter partitionFilter; + private final long taskId; + private final long maxRecords; + + @SuppressWarnings("deprecation") + public QueryCommandProxy( + GrpcCallExecutor executor, + RecordSequenceListener listener, QueryPolicy queryPolicy, + Statement statement, + long taskId, + PartitionFilter partitionFilter, + PartitionTracker partitionTracker + ) { + super(false, QueryGrpc.getQueryStreamingMethod(), executor, queryPolicy, listener, partitionTracker); + this.statement = statement; + this.partitionFilter = partitionFilter; + this.taskId = taskId; + this.maxRecords = statement.getMaxRecords() > 0 ? statement.getMaxRecords() : queryPolicy.maxRecords; + } + + @Override + protected Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + // Set the query parameters in the Aerospike request payload. + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + Kvs.QueryRequest.Builder queryRequestBuilder = Kvs.QueryRequest.newBuilder(); + + queryRequestBuilder.setQueryPolicy(GrpcConversions.toGrpc((QueryPolicy)policy)); + + if (partitionFilter != null) { + queryRequestBuilder.setPartitionFilter(GrpcConversions.toGrpc(partitionFilter)); + } + + queryRequestBuilder.setStatement(GrpcConversions.toGrpc(statement, taskId, maxRecords)); + builder.setQueryRequest(queryRequestBuilder.build()); + return builder; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ReadCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ReadCommandProxy.java new file mode 100644 index 000000000..4db4255c4 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ReadCommandProxy.java @@ -0,0 +1,154 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.RecordListener; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.MethodDescriptor; + +public class ReadCommandProxy extends SingleCommandProxy { + private final RecordListener listener; + final Key key; + private final String[] binNames; + private final boolean isOperation; + + public ReadCommandProxy( + GrpcCallExecutor executor, + RecordListener listener, + Policy policy, + Key key, + String[] binNames + ) { + super(KVSGrpc.getReadStreamingMethod(), executor, policy); + this.listener = listener; + this.key = key; + this.binNames = binNames; + this.isOperation = false; + } + + public ReadCommandProxy( + MethodDescriptor methodDescriptor, + GrpcCallExecutor executor, + RecordListener listener, + Policy policy, + Key key, + boolean isOperation + ) { + super(methodDescriptor, executor, policy); + this.listener = listener; + this.key = key; + this.binNames = null; + this.isOperation = isOperation; + } + + @Override + void writeCommand(Command command) { + command.setRead(policy, key, binNames); + } + + @Override + void parseResult(Parser parser) { + Record record = parseRecordResult(parser); + + try { + listener.onSuccess(key, record); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + protected final Record parseRecordResult(Parser parser) { + Record record = null; + int resultCode = parser.parseHeader(); + + switch (resultCode) { + case ResultCode.OK: + parser.skipKey(); + if (parser.opCount == 0) { + // Bin data was not returned. + record = new Record(null, parser.generation, parser.expiration); + } + else { + record = parser.parseRecord(isOperation); + } + break; + + case ResultCode.KEY_NOT_FOUND_ERROR: + handleNotFound(resultCode); + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + break; + + case ResultCode.UDF_BAD_RESPONSE: + parser.skipKey(); + record = parser.parseRecord(isOperation); + handleUdfError(record, resultCode); + break; + + default: + throw new AerospikeException(resultCode); + } + + return record; + } + + protected void handleNotFound(int resultCode) { + // Do nothing in default case. Record will be null. + } + + private void handleUdfError(Record record, int resultCode) { + String ret = (String)record.bins.get("FAILURE"); + + if (ret == null) { + throw new AerospikeException(resultCode); + } + + String message; + int code; + + try { + String[] list = ret.split(":"); + code = Integer.parseInt(list[2].trim()); + message = list[0] + ':' + list[1] + ' ' + list[3]; + } + catch (Exception e) { + // Use generic exception if parse error occurs. + throw new AerospikeException(resultCode, ret); + } + + throw new AerospikeException(code, message); + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ReadHeaderCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ReadHeaderCommandProxy.java new file mode 100644 index 000000000..281382321 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ReadHeaderCommandProxy.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.RecordListener; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class ReadHeaderCommandProxy extends SingleCommandProxy { + private final RecordListener listener; + private final Key key; + + public ReadHeaderCommandProxy( + GrpcCallExecutor executor, + RecordListener listener, + Policy policy, + Key key + ) { + super(KVSGrpc.getGetHeaderStreamingMethod(), executor, policy); + this.listener = listener; + this.key = key; + } + + @Override + void writeCommand(Command command) { + command.setReadHeader(policy, key); + } + + @Override + void parseResult(Parser parser) { + Record record = null; + int resultCode = parser.parseHeader(); + + switch (resultCode) { + case ResultCode.OK: + record = new Record(null, parser.generation, parser.expiration); + break; + + case ResultCode.KEY_NOT_FOUND_ERROR: + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + break; + + default: + throw new AerospikeException(resultCode); + } + + try { + listener.onSuccess(key, record); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/RecordProxy.java b/proxy/src/com/aerospike/client/proxy/RecordProxy.java new file mode 100644 index 000000000..2e5d5ea78 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/RecordProxy.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.query.BVal; + +public class RecordProxy { + /** + * Optional Key. + */ + public final Key key; + + /** + * Optional Record result after command has completed. + */ + public final Record record; + + /** + * Optional bVal. + */ + public final BVal bVal; + + /** + * The result code from proxy server. + */ + public final int resultCode; + + public RecordProxy(int resultCode, Key key, Record record, BVal bVal) { + this.resultCode = resultCode; + this.key = key; + this.record = record; + this.bVal = bVal; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/RecordSequenceListenerToCallback.java b/proxy/src/com/aerospike/client/proxy/RecordSequenceListenerToCallback.java new file mode 100644 index 000000000..3af4230ce --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/RecordSequenceListenerToCallback.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.CompletableFuture; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.ScanCallback; +import com.aerospike.client.listener.RecordSequenceListener; + +class RecordSequenceListenerToCallback implements RecordSequenceListener { + private final ScanCallback callback; + private final CompletableFuture future; + + public RecordSequenceListenerToCallback(ScanCallback callback, CompletableFuture future) { + this.callback = callback; + this.future = future; + } + + @Override + public void onRecord(Key key, Record record) throws AerospikeException { + callback.scanCallback(key, record); + } + + @Override + public void onSuccess() { + future.complete(null); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } +} \ No newline at end of file diff --git a/proxy/src/com/aerospike/client/proxy/RecordSequenceRecordSet.java b/proxy/src/com/aerospike/client/proxy/RecordSequenceRecordSet.java new file mode 100644 index 000000000..0da743a84 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/RecordSequenceRecordSet.java @@ -0,0 +1,171 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Log; +import com.aerospike.client.Record; +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.query.KeyRecord; +import com.aerospike.client.query.RecordSet; + +/** + * A {@link RecordSequenceListener} that implements a {@link RecordSet}. + */ +public class RecordSequenceRecordSet extends RecordSet implements RecordSequenceListener { + private final long taskId; + private volatile boolean valid = true; + private final BlockingQueue queue; + private volatile KeyRecord record; + private volatile AerospikeException exception; + + public RecordSequenceRecordSet(long taskId, int capacity) { + this.queue = new ArrayBlockingQueue<>(capacity); + this.taskId = taskId; + } + + /** + * Retrieve next record. This method will block until a record is retrieved + * or the query is cancelled. + * + * @return whether record exists - if false, no more records are available + */ + public boolean next() throws AerospikeException { + if (!valid) { + checkForException(); + return false; + } + + try { + record = queue.take(); + } + catch (InterruptedException ie) { + valid = false; + + if (Log.debugEnabled()) { + Log.debug("RecordSet " + taskId + " take " + + "interrupted"); + } + return false; + } + + if (record == END) { + valid = false; + checkForException(); + return false; + } + return true; + } + + private void checkForException() { + if (exception != null) { + abort(); + throw exception; + } + } + + protected void abort() { + valid = false; + queue.clear(); + + // Send end command to transaction thread. + // It's critical that the end offer succeeds. + while (!queue.offer(END)) { + // Queue must be full. Remove one item to make room. + if (queue.poll() == null) { + // Can't offer or poll. Nothing further can be done. + if (Log.debugEnabled()) { + Log.debug("RecordSet " + taskId + " both offer and poll failed on abort"); + } + break; + } + } + } + + public void close() { + valid = false; + } + + @Override + public Record getRecord() { + return record.record; + } + + @Override + public Key getKey() { + return record.key; + } + + @Override + public void onRecord(Key key, Record record) throws AerospikeException { + if (!valid) { + // Abort the query. + throw new AerospikeException.QueryTerminated(); + } + + try { + // This put will block if queue capacity is reached. + queue.put(new KeyRecord(key, record)); + } + catch (InterruptedException ie) { + if (Log.debugEnabled()) { + Log.debug("RecordSet " + taskId + " put interrupted"); + } + + // Valid may have changed. Check again. + if (valid) { + abort(); + } + + // Abort the query. + throw new AerospikeException.QueryTerminated(); + } + } + + @Override + public void onSuccess() { + if (!valid) { + return; + } + + try { + // This put will block if queue capacity is reached. + queue.put(END); + } + catch (InterruptedException ie) { + if (Log.debugEnabled()) { + Log.debug("RecordSet " + taskId + " put interrupted"); + } + + // Valid may have changed. Check again. + if (valid) { + abort(); + } + } + } + + @Override + public void onFailure(AerospikeException ae) { + exception = ae; + abort(); + } +} + diff --git a/proxy/src/com/aerospike/client/proxy/RecordSequenceToQueryListener.java b/proxy/src/com/aerospike/client/proxy/RecordSequenceToQueryListener.java new file mode 100644 index 000000000..a301c2b87 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/RecordSequenceToQueryListener.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.concurrent.CompletableFuture; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.Record; +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.query.QueryListener; + +class RecordSequenceToQueryListener implements RecordSequenceListener { + private final QueryListener listener; + private final CompletableFuture future; + + public RecordSequenceToQueryListener(QueryListener listener, CompletableFuture future) { + this.listener = listener; + this.future = future; + } + + @Override + public void onRecord(Key key, Record record) throws AerospikeException { + listener.onRecord(key, record); + } + + @Override + public void onSuccess() { + future.complete(null); + } + + @Override + public void onFailure(AerospikeException ae) { + future.completeExceptionally(ae); + } +} \ No newline at end of file diff --git a/proxy/src/com/aerospike/client/proxy/ResultSetProxy.java b/proxy/src/com/aerospike/client/proxy/ResultSetProxy.java new file mode 100644 index 000000000..1052b3c99 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ResultSetProxy.java @@ -0,0 +1,207 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Log; +import com.aerospike.client.query.ResultSet; + +/** + * This class manages result retrieval from queries. + * Multiple threads will retrieve results from the server nodes and put these results on the queue. + * The single user thread consumes these results from the queue. + */ +public class ResultSetProxy extends ResultSet { + private final QueryAggregateCommandProxy queryAggregateCommand; + + private final BlockingQueue queue; + + private volatile boolean valid = true; + + private Object row; + + /** + * Initialize result set with underlying producer/consumer queue. + */ + protected ResultSetProxy(QueryAggregateCommandProxy queryAggregateCommand, int capacity) { + this.queryAggregateCommand = queryAggregateCommand; + this.queue = new ArrayBlockingQueue<>(capacity); + } + + //------------------------------------------------------- + // Result traversal methods + //------------------------------------------------------- + + /** + * Retrieve next result. This method will block until a result is retrieved + * or the query is cancelled. + * + * @return whether result exists - if false, no more results are available + */ + public final boolean next() throws AerospikeException { + if (!valid) { + queryAggregateCommand.checkForException(); + return false; + } + + try { + row = queue.take(); + } + catch (InterruptedException ie) { + valid = false; + + if (Log.debugEnabled()) { + Log.debug("ResultSet " + queryAggregateCommand.getTaskId() + " take " + + "interrupted"); + } + return false; + } + + if (row == END) { + valid = false; + queryAggregateCommand.checkForException(); + return false; + } + return true; + } + + /** + * Close query. + */ + public final void close() { + valid = false; + + // Check if more results are available. + if (row != END && queue.poll() != END) { + // Some query threads may still be running. Stop these threads. + queryAggregateCommand.stop(new AerospikeException.QueryTerminated()); + } + } + + /** + * Provide Iterator for RecordSet. + */ + @Override + public Iterator iterator() { + return new ResultSetIterator(this); + } + + //------------------------------------------------------- + // Meta-data retrieval methods + //------------------------------------------------------- + + /** + * Get result. + */ + public final Object getObject() { + return row; + } + + //------------------------------------------------------- + // Methods for internal use only. + //------------------------------------------------------- + + /** + * Put object on the queue. + */ + public final boolean put(Object object) { + if (!valid) { + return false; + } + + try { + // This put will block if queue capacity is reached. + queue.put(object); + return true; + } + catch (InterruptedException ie) { + if (Log.debugEnabled()) { + Log.debug("ResultSet " + queryAggregateCommand.getTaskId() + " put " + + "interrupted"); + } + + // Valid may have changed. Check again. + if (valid) { + abort(); + } + return false; + } + } + + /** + * Abort retrieval with end token. + */ + public final void abort() { + valid = false; + queue.clear(); + + // Send end command to transaction thread. + // It's critical that the end offer succeeds. + while (!queue.offer(END)) { + // Queue must be full. Remove one item to make room. + if (queue.poll() == null) { + // Can't offer or poll. Nothing further can be done. + if (Log.debugEnabled()) { + Log.debug("ResultSet " + queryAggregateCommand.getTaskId() + " both" + + " " + + "offer and poll failed on abort"); + } + break; + } + } + } + + /** + * Support standard iteration interface for RecordSet. + */ + private static class ResultSetIterator implements Iterator, Closeable { + + private final ResultSetProxy resultSet; + private boolean more; + + ResultSetIterator(ResultSetProxy resultSet) { + this.resultSet = resultSet; + more = this.resultSet.next(); + } + + @Override + public boolean hasNext() { + return more; + } + + @Override + public Object next() { + Object obj = resultSet.row; + more = resultSet.next(); + return obj; + } + + @Override + public void remove() { + } + + @Override + public void close() { + resultSet.close(); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ScanCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ScanCommandProxy.java new file mode 100644 index 000000000..250315275 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ScanCommandProxy.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.client.query.PartitionFilter; +import com.aerospike.client.query.PartitionTracker; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.ScanGrpc; + +/** + * Implements asynchronous scan for the proxy. + */ +public class ScanCommandProxy extends ScanQueryBaseCommandProxy { + private final String namespace; + private final String setName; + private final String[] binNames; + private final PartitionFilter partitionFilter; + + public ScanCommandProxy( + GrpcCallExecutor executor, + ScanPolicy scanPolicy, + RecordSequenceListener listener, String namespace, + String setName, + String[] binNames, + PartitionFilter partitionFilter, + PartitionTracker partitionTracker + ) { + super(true, ScanGrpc.getScanStreamingMethod(), executor, scanPolicy, + listener, partitionTracker); + this.namespace = namespace; + this.setName = setName; + this.binNames = binNames; + this.partitionFilter = partitionFilter; + } + + @Override + Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + // Set the scan parameters in the Aerospike request payload. + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + Kvs.ScanRequest.Builder scanRequestBuilder = Kvs.ScanRequest.newBuilder(); + + scanRequestBuilder.setScanPolicy(GrpcConversions.toGrpc((ScanPolicy)policy)); + scanRequestBuilder.setNamespace(namespace); + + if (setName != null) { + scanRequestBuilder.setSetName(setName); + } + + if (binNames != null) { + for (String binName : binNames) { + scanRequestBuilder.addBinNames(binName); + } + } + + if (partitionFilter != null) { + scanRequestBuilder.setPartitionFilter(GrpcConversions.toGrpc(partitionFilter)); + } + + builder.setScanRequest(scanRequestBuilder.build()); + return builder; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/ScanQueryBaseCommandProxy.java b/proxy/src/com/aerospike/client/proxy/ScanQueryBaseCommandProxy.java new file mode 100644 index 000000000..60d4fe38c --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/ScanQueryBaseCommandProxy.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import java.util.Collections; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.ResultCode; +import com.aerospike.client.cluster.Node; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.RecordSequenceListener; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.client.query.PartitionTracker; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.MethodDescriptor; + +/** + * Base class for Scan and Query. + */ +abstract class ScanQueryBaseCommandProxy extends MultiCommandProxy { + private final RecordSequenceListener listener; + private final PartitionTracker partitionTracker; + protected final PartitionTracker.NodePartitions dummyNodePartitions; + private final boolean isScan; + + public ScanQueryBaseCommandProxy( + boolean isScan, + MethodDescriptor methodDescriptor, + GrpcCallExecutor executor, + Policy policy, + RecordSequenceListener listener, + PartitionTracker partitionTracker + ) { + super(methodDescriptor, executor, policy); + this.isScan = isScan; + this.listener = listener; + this.partitionTracker = partitionTracker; + this.dummyNodePartitions = new PartitionTracker.NodePartitions(null, Node.PARTITIONS); + } + + @Override + protected void writeCommand(Command command) { + // Nothing to do since there is no Aerospike payload. + } + + @Override + void parseResult(Parser parser) { + RecordProxy recordProxy = parseRecordResult(parser, false, true, !isScan); + + if ((parser.info3 & Command.INFO3_PARTITION_DONE) != 0) { + // When an error code is received, mark partition as unavailable + // for the current round. Unavailable partitions will be retried + // in the next round. Generation is overloaded as partitionId. + if (partitionTracker != null && recordProxy.resultCode != ResultCode.OK) { + partitionTracker.partitionUnavailable(dummyNodePartitions, parser.generation); + } + return; + } + + if (recordProxy.resultCode == ResultCode.OK && !super.hasNext) { + if (partitionTracker != null && !partitionTracker.isComplete(false, policy, Collections.singletonList(dummyNodePartitions))) { + retry(); + return; + } + + // This is the end of scan marker record. + listener.onSuccess(); + return; + } + + if (recordProxy.resultCode != ResultCode.OK) { + throw new AerospikeException(recordProxy.resultCode); + } + + listener.onRecord(recordProxy.key, recordProxy.record); + + if (partitionTracker != null) { + if (isScan) { + partitionTracker.setDigest(dummyNodePartitions, recordProxy.key); + } + else { + partitionTracker.setLast(dummyNodePartitions, recordProxy.key, + recordProxy.bVal.val); + } + } + } + + @Override + void onFailure(AerospikeException ae) { + if (partitionTracker != null && partitionTracker.shouldRetry(dummyNodePartitions, ae)) { + if (retry()) { + return; + } + // Retry failed. Notify the listener. + } + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/SingleCommandProxy.java b/proxy/src/com/aerospike/client/proxy/SingleCommandProxy.java new file mode 100644 index 000000000..7c2e40e8b --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/SingleCommandProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.MethodDescriptor; + +public abstract class SingleCommandProxy extends CommandProxy { + + public SingleCommandProxy( + MethodDescriptor methodDescriptor, + GrpcCallExecutor executor, + Policy policy + ) { + super(methodDescriptor, executor, policy, 1); + } + + void onResponse(Kvs.AerospikeResponsePayload response) { + // Check response status for client errors (negative error codes). + // Server errors are checked in response payload in Parser. + int status = response.getStatus(); + + if (status != 0) { + notifyFailure(new AerospikeException(status)); + return; + } + + byte[] bytes = response.getPayload().toByteArray(); + Parser parser = new Parser(bytes); + parser.parseProto(); + parseResult(parser); + } + + abstract void parseResult(Parser parser); +} diff --git a/proxy/src/com/aerospike/client/proxy/TouchCommandProxy.java b/proxy/src/com/aerospike/client/proxy/TouchCommandProxy.java new file mode 100644 index 000000000..b75fd4c74 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/TouchCommandProxy.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Key; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.WriteListener; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class TouchCommandProxy extends SingleCommandProxy { + private final WriteListener listener; + private final WritePolicy writePolicy; + private final Key key; + + public TouchCommandProxy( + GrpcCallExecutor executor, + WriteListener listener, + WritePolicy writePolicy, + Key key + ) { + super(KVSGrpc.getTouchStreamingMethod(), executor, writePolicy); + this.listener = listener; + this.writePolicy = writePolicy; + this.key = key; + } + + @Override + void writeCommand(Command command) { + command.setTouch(writePolicy, key); + } + + @Override + void parseResult(Parser parser) { + int resultCode = parser.parseResultCode(); + + switch (resultCode) { + case ResultCode.OK: + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + break; + + default: + throw new AerospikeException(resultCode); + } + + try { + listener.onSuccess(key); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/WriteCommandProxy.java b/proxy/src/com/aerospike/client/proxy/WriteCommandProxy.java new file mode 100644 index 000000000..209cc5f0b --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/WriteCommandProxy.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Bin; +import com.aerospike.client.Key; +import com.aerospike.client.Operation; +import com.aerospike.client.ResultCode; +import com.aerospike.client.command.Command; +import com.aerospike.client.listener.WriteListener; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.proxy.grpc.GrpcCallExecutor; +import com.aerospike.proxy.client.KVSGrpc; + +public final class WriteCommandProxy extends SingleCommandProxy { + private final WriteListener listener; + private final WritePolicy writePolicy; + private final Key key; + private final Bin[] bins; + private final Operation.Type type; + + public WriteCommandProxy( + GrpcCallExecutor executor, + WriteListener listener, + WritePolicy writePolicy, + Key key, + Bin[] bins, + Operation.Type type + ) { + super(KVSGrpc.getWriteStreamingMethod(), executor, writePolicy); + this.listener = listener; + this.writePolicy = writePolicy; + this.key = key; + this.bins = bins; + this.type = type; + } + + @Override + void writeCommand(Command command) { + command.setWrite(writePolicy, type, key, bins); + } + + @Override + void parseResult(Parser parser) { + int resultCode = parser.parseResultCode(); + + switch (resultCode) { + case ResultCode.OK: + break; + + case ResultCode.FILTERED_OUT: + if (policy.failOnFilteredOut) { + throw new AerospikeException(resultCode); + } + break; + + default: + throw new AerospikeException(resultCode); + } + + try { + listener.onSuccess(key); + } + catch (Throwable t) { + logOnSuccessError(t); + } + } + + @Override + void onFailure(AerospikeException ae) { + listener.onFailure(ae); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/auth/AuthTokenManager.java b/proxy/src/com/aerospike/client/proxy/auth/AuthTokenManager.java new file mode 100644 index 000000000..bea78f102 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/auth/AuthTokenManager.java @@ -0,0 +1,409 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.auth; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.proxy.auth.credentials.BearerTokenCallCredentials; +import com.aerospike.client.proxy.grpc.GrpcChannelProvider; +import com.aerospike.client.proxy.grpc.GrpcConversions; +import com.aerospike.proxy.client.Auth; +import com.aerospike.proxy.client.AuthServiceGrpc; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import io.grpc.CallOptions; +import io.grpc.Deadline; +import io.grpc.ManagedChannel; +import io.grpc.stub.StreamObserver; + +/** + * An access token manager for Aerospike proxy. + */ +public class AuthTokenManager implements Closeable { + /** + * A conservative estimate of minimum amount of time in millis it takes for + * token refresh to complete. Auto refresh should be scheduled at least + * this amount before expiry, i.e, if remaining expiry time is less than + * this amount refresh should be scheduled immediately. + */ + private static final int refreshMinTime = 5000; + + /** + * A cap on refresh time in millis to throttle an auto refresh requests in + * case of token refresh failure. + */ + private static final int maxExponentialBackOff = 15000; + + /** + * Fraction of token expiry time to elapse before scheduling an auto + * refresh. + * + * @see AuthTokenManager#refreshMinTime + */ + private static final float refreshAfterFraction = 0.95f; + + /** + * An {@link ObjectMapper} to parse access token. + */ + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final ClientPolicy clientPolicy; + private final GrpcChannelProvider channelProvider; + private final ScheduledExecutorService executor; + private final AtomicBoolean isFetchingToken = new AtomicBoolean(false); + private final AtomicBoolean isClosed = new AtomicBoolean(false); + /** + * Count of consecutive errors while refreshing the token. + */ + private final AtomicInteger consecutiveRefreshErrors = new AtomicInteger(0); + + /** + * The error encountered when refreshing the token. It will be null when + * {@link #consecutiveRefreshErrors} is zero. + */ + private final AtomicReference refreshError = + new AtomicReference<>(null); + + private volatile AccessToken accessToken; + private volatile boolean fetchScheduled; + /** + * A {@link ScheduledFuture} holding reference to the next auto schedule task. + */ + private ScheduledFuture refreshFuture; + + public AuthTokenManager(ClientPolicy clientPolicy, GrpcChannelProvider grpcCallExecutor) { + this.clientPolicy = clientPolicy; + this.channelProvider = grpcCallExecutor; + this.executor = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder().setNameFormat("aerospike-auth-manager").build()); + this.accessToken = new AccessToken(System.currentTimeMillis(), 0, ""); + fetchToken(true); + } + + /** + * Fetch the new token if expired or scheduled for auto refresh. + * + * @param forceRefresh A boolean flag to refresh token forcefully. This is required for initialization and auto + * refresh. Auto refresh will get rejected as token won't be expired at that time, but we need + * to refresh it beforehand. If true, this function will run from the invoking thread, + * not from the scheduler. + */ + private void fetchToken(boolean forceRefresh) { + fetchScheduled = false; + if (isClosed.get() || !isTokenRequired() || isFetchingToken.get()) { + return; + } + if (shouldRefresh(forceRefresh)) { + try { + if (Log.debugEnabled()) { + Log.debug("Starting token refresh"); + } + Auth.AerospikeAuthRequest aerospikeAuthRequest = Auth.AerospikeAuthRequest.newBuilder() + .setUsername(clientPolicy.user).setPassword(clientPolicy.password).build(); + ManagedChannel channel = channelProvider.getControlChannel(); + if (channel == null) { + isFetchingToken.set(false); + // Channel is unavailable. Try again. + unsafeScheduleRefresh(10, true); + return; + } + + isFetchingToken.set(true); + AuthServiceGrpc.newStub(channel).withDeadline(Deadline.after(refreshMinTime, TimeUnit.MILLISECONDS)) + .get(aerospikeAuthRequest, new StreamObserver() { + @Override + public void onNext(Auth.AerospikeAuthResponse aerospikeAuthResponse) { + try { + accessToken = + parseToken(aerospikeAuthResponse.getToken()); + if (Log.debugEnabled()) { + Log.debug(String.format("Fetched token successfully " + + "with TTL %d", accessToken.ttl)); + } + unsafeScheduleNextRefresh(); + clearRefreshErrors(); + } + catch (Exception e) { + onFetchError(e); + } + } + + @Override + public void onError(Throwable t) { + onFetchError(t); + } + + @Override + public void onCompleted() { + isFetchingToken.set(false); + } + }); + + } + catch (Exception e) { + onFetchError(e); + } + } + } + + private void clearRefreshErrors() { + consecutiveRefreshErrors.set(0); + refreshError.set(null); + } + + private void updateRefreshErrors(Throwable t) { + consecutiveRefreshErrors.incrementAndGet(); + refreshError.set(t); + } + + private void onFetchError(Throwable t) { + updateRefreshErrors(t); + Exception e = new Exception("Error fetching access token", t); + Log.error(GrpcConversions.getDisplayMessage(e, GrpcConversions.MAX_ERR_MSG_LENGTH)); + unsafeScheduleNextRefresh(); + isFetchingToken.set(false); + } + + private boolean shouldRefresh(boolean forceRefresh) { + return forceRefresh || !isTokenValid(); + } + + private void unsafeScheduleNextRefresh() { + long ttl = accessToken.ttl; + long delay = (long)Math.floor(ttl * refreshAfterFraction); + + if (ttl - delay < refreshMinTime) { + // We need at least refreshMinTimeMillis to refresh, schedule + // immediately. + delay = ttl - refreshMinTime; + } + + if (!isTokenValid()) { + // Force immediate refresh. + delay = 0; + } + + if (delay == 0 && consecutiveRefreshErrors.get() > 0) { + // If we continue to fail then schedule will be too aggressive on fetching new token. Avoid that by increasing + // fetch delay. + + delay = (long)(Math.pow(2, consecutiveRefreshErrors.get()) * 1000); + if (delay > maxExponentialBackOff) { + delay = maxExponentialBackOff; + } + + // Handle wrap around. + if (delay < 0) { + delay = 0; + } + } + unsafeScheduleRefresh(delay, true); + } + + private void unsafeScheduleRefresh(long delay, boolean forceRefresh) { + if (isClosed.get() || !forceRefresh || fetchScheduled) { + return; + } + if (!executor.isShutdown()) { + //noinspection ConstantValue + refreshFuture = executor.schedule(() -> fetchToken(forceRefresh), delay, TimeUnit.MILLISECONDS); + fetchScheduled = true; + if (Log.debugEnabled()) { + Log.debug(String.format("Scheduled refresh after %d millis", delay)); + } + } + } + + private boolean isTokenRequired() { + return clientPolicy.user != null; + } + + private AccessToken parseToken(String token) throws IOException { + String claims = token.split("\\.")[1]; + byte[] decodedClaims = Base64.getDecoder().decode(claims); + @SuppressWarnings("unchecked") + Map parsedClaims = objectMapper.readValue(decodedClaims, Map.class); + Object expiryToken = parsedClaims.get("exp"); + Object iat = parsedClaims.get("iat"); + if (expiryToken instanceof Integer && iat instanceof Integer) { + int ttl = ((Integer)expiryToken - (Integer)iat) * 1000; + if (ttl <= 0) { + throw new IllegalArgumentException("token 'iat' > 'exp'"); + } + // Set expiry based on local clock. + long expiry = System.currentTimeMillis() + ttl; + return new AccessToken(expiry, ttl, token); + } + else { + throw new IllegalArgumentException("Unsupported access token format"); + } + } + + public CallOptions setCallCredentials(CallOptions callOptions) { + if (isTokenRequired()) { + if (!isTokenValid()) { + if (Log.warnEnabled()) { + // TODO: This warns for evey call, spamming the output. + // Should be rate limited. Possibly once in a few seconds. + // This alerts that auto refresh didn't finish correctly. In normal scenario, this should never + // happen. + Log.warn("Trying to refresh token before setting into call"); + } + unsafeScheduleRefresh(0, false); + } + if (!isTokenValid()) { + throw new IllegalStateException("Access token has expired"); + } + return callOptions.withCallCredentials(new BearerTokenCallCredentials(accessToken.token)); + } + return callOptions; + } + + /** + * @return the minimum amount of time it takes for the token to refresh. + */ + public int getRefreshMinTime() { + return refreshMinTime; + } + + private boolean isTokenValid() { + AccessToken token = accessToken; + return !isTokenRequired() || (token != null && !token.hasExpired()); + } + + public TokenStatus getTokenStatus() { + if (isTokenValid()) { + return new TokenStatus(); + } + + Throwable error = refreshError.get(); + if (error != null) { + return new TokenStatus(error); + } + + AccessToken token = accessToken; + if (token != null && token.hasExpired()) { + return new TokenStatus(new AerospikeException(ResultCode.NOT_AUTHENTICATED, + "token has expired")); + } + + return new TokenStatus(new AerospikeException(ResultCode.NOT_AUTHENTICATED)); + } + + @Override + public void close() { + if (isClosed.getAndSet(true)) { + return; + } + + // TODO copied from java.util.concurrent.ExecutorService#close available from Java 19. + boolean terminated = executor.isTerminated(); + if (!terminated) { + if (refreshFuture != null) { + refreshFuture.cancel(true); + } + executor.shutdown(); + boolean interrupted = false; + while (!terminated) { + try { + terminated = executor.awaitTermination(1L, TimeUnit.DAYS); + } + catch (InterruptedException e) { + if (!interrupted) { + executor.shutdownNow(); + interrupted = true; + } + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + public static class TokenStatus { + private final Throwable error; + private final Boolean valid; + + public TokenStatus() { + this.valid = true; + this.error = null; + } + + public TokenStatus(Throwable error) { + this.valid = false; + this.error = error; + } + + /** + * @return true iff the token is valid. + */ + public Boolean isValid() { + return valid; + } + + /** + * Get the token fetch error. Should be used only when {@link #isValid()} + * returns false. + * + * @return the token fetch error. + */ + public Throwable getError() { + return error; + } + } + + private static class AccessToken { + /** + * Local token expiry timestamp in millis. + */ + private final long expiry; + /** + * Remaining time to live for the token in millis. + */ + private final long ttl; + /** + * An access token for Aerospike proxy. + */ + private final String token; + + public AccessToken(long expiry, long ttl, String token) { + this.expiry = expiry; + this.ttl = ttl; + this.token = token; + } + + public boolean hasExpired() { + return System.currentTimeMillis() > expiry; + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/auth/credentials/BearerTokenCallCredentials.java b/proxy/src/com/aerospike/client/proxy/auth/credentials/BearerTokenCallCredentials.java new file mode 100644 index 000000000..6216e5ba0 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/auth/credentials/BearerTokenCallCredentials.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.auth.credentials; + +import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; + +import java.util.concurrent.Executor; + +import io.grpc.CallCredentials; +import io.grpc.Metadata; +import io.grpc.Status; + +/** + * A {@link CallCredentials} implementation to access Aerospike proxy. + */ +public class BearerTokenCallCredentials extends CallCredentials { + private static final String BEARER_TYPE = "Bearer"; + private static final Metadata.Key AUTHORIZATION_METADATA_KEY = Metadata.Key.of("Authorization", ASCII_STRING_MARSHALLER); + + private final String value; + + public BearerTokenCallCredentials(String value) { + this.value = value; + } + + @Override + public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) { + executor.execute(() -> { + try { + Metadata headers = new Metadata(); + headers.put(AUTHORIZATION_METADATA_KEY, String.format("%s %s", BEARER_TYPE, value)); + metadataApplier.apply(headers); + } + catch (Throwable e) { + metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)); + } + }); + } + + @Override + public void thisUsesUnstableApi() { + // noop + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcChannelSelector.java b/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcChannelSelector.java new file mode 100644 index 000000000..ce6ed0047 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcChannelSelector.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +/** + * A default gRPC stream selector which selects channel by the low and high + * water mark. + */ +public class DefaultGrpcChannelSelector implements GrpcChannelSelector { + private final int requestsLowWaterMark; + private final int requestsHighWaterMark; + private final Random random = new Random(); + + public DefaultGrpcChannelSelector(int requestsLowWaterMark, int requestsHighWaterMark) { + this.requestsLowWaterMark = requestsLowWaterMark; + this.requestsHighWaterMark = requestsHighWaterMark; + } + + @Override + public GrpcChannelExecutor select(List channels, GrpcStreamingCall call) { + // Sort by channel id. Leave original list as it is. + channels = new ArrayList<>(channels); + channels.sort(Comparator.comparingLong(GrpcChannelExecutor::getId)); + + // Select the first channel below the low watermark. + for (GrpcChannelExecutor channel : channels) { + if (channel.getOngoingRequests() < requestsLowWaterMark) { + return channel; + } + } + + // FIXME: it might be the case that the channel has opened + // maxConcurrentStreams but none of them are for this grpcCall. This + // also needs to be checked when selecting the channel. + + // All channels are above the low watermark, select the first channel + // below the high watermark. + for (GrpcChannelExecutor channel : channels) { + if (channel.getOngoingRequests() < requestsHighWaterMark) { + return channel; + } + } + + // TODO: maybe we should use in-flight bytes, number of streams, or + // some other parameter to select the channel. + // All channels are above the high water mark, select random channel. + return channels.get(random.nextInt(channels.size())); + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcStreamSelector.java b/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcStreamSelector.java new file mode 100644 index 000000000..275f49ba9 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/DefaultGrpcStreamSelector.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import com.aerospike.proxy.client.KVSGrpc; +import com.aerospike.proxy.client.Kvs; +import com.aerospike.proxy.client.QueryGrpc; +import com.aerospike.proxy.client.ScanGrpc; + +/** + * A default gRPC stream selector which selects a free stream. + */ +public class DefaultGrpcStreamSelector implements GrpcStreamSelector { + private final int maxConcurrentStreamsPerChannel; + private final int maxConcurrentRequestsPerStream; + private final int totalRequestsPerStream; + + /** + * Streaming calls with less than these many responses will be + * multiplexed on the same stream. + */ + private static final int LARGE_RESPONSE_CUTOFF = 10; + + public DefaultGrpcStreamSelector(int maxConcurrentStreamsPerChannel, int maxConcurrentRequestsPerStream, int totalRequestsPerStream) { + this.maxConcurrentStreamsPerChannel = maxConcurrentStreamsPerChannel; + this.maxConcurrentRequestsPerStream = maxConcurrentRequestsPerStream; + this.totalRequestsPerStream = totalRequestsPerStream; + } + + @Override + public SelectedStream select(List streams, GrpcStreamingCall call) { + final String fullMethodName = + call.getStreamingMethodDescriptor().getFullMethodName(); + + // Always use a non-multiplexed new stream for a scan, long query, and + // a large batch. + if (isScan(call) || isLongQuery(call) || isLargeBatch(call)) { + return new SelectedStream(1, 1); + } + + // Sort by stream id. Leave original list as it is. + List filteredStreams = streams.stream() + .filter(grpcStream -> + grpcStream.getMethodDescriptor().getFullMethodName() + .equals(fullMethodName) && grpcStream.canEnqueue() + ) + .sorted(Comparator.comparingInt(GrpcStream::getId)) + .collect(Collectors.toList()); + + // Select first stream with less than max concurrent requests. + for (GrpcStream stream : filteredStreams) { + if (stream.getOngoingRequests() < stream.getMaxConcurrentRequests()) { + return new SelectedStream(stream); + } + } + + if (streams.size() < maxConcurrentStreamsPerChannel) { + // Create new stream. + return new SelectedStream(maxConcurrentRequestsPerStream, totalRequestsPerStream); + } + + // TODO What is the probability of this occurring? Should some streams + // in a channel be reserved for rarely used API's? + if (filteredStreams.isEmpty()) { + // No slots to create a new stream. + return null; + } + + // Select stream with lowest percent of total requests executed. + GrpcStream selected = filteredStreams.get(0); + for (GrpcStream stream : filteredStreams) { + float executedPercent = + (float)stream.getExecutedRequests() / stream.getTotalRequestsToExecute(); + float selectedPercent = + (float)selected.getExecutedRequests() / stream.getTotalRequestsToExecute(); + if (executedPercent < selectedPercent) { + selected = stream; + } + } + return new SelectedStream(selected); + } + + private boolean isLargeBatch(GrpcStreamingCall call) { + String fullMethodName = + call.getStreamingMethodDescriptor().getFullMethodName(); + + String batchFullMethodName = + KVSGrpc.getBatchOperateMethod().getFullMethodName(); + String batchStreamingFullMethodName = + KVSGrpc.getBatchOperateStreamingMethod().getFullMethodName(); + + if(!batchFullMethodName.equals(fullMethodName) && + !batchStreamingFullMethodName.equals(fullMethodName)) { + return false; // Not a batch method. + } + + return call.getNumExpectedResponses() < LARGE_RESPONSE_CUTOFF; + } + + private boolean isScan(GrpcStreamingCall call) { + String fullMethodName = + call.getStreamingMethodDescriptor().getFullMethodName(); + String scanFullMethodName = + ScanGrpc.getScanMethod().getFullMethodName(); + String scanStreamingFullMethodName = + ScanGrpc.getScanStreamingMethod().getFullMethodName(); + return scanFullMethodName.equals(fullMethodName) || + scanStreamingFullMethodName.equals(fullMethodName); + } + + private boolean isLongQuery(GrpcStreamingCall call) { + String fullMethodName = + call.getStreamingMethodDescriptor().getFullMethodName(); + String queryFullMethodName = + QueryGrpc.getQueryMethod().getFullMethodName(); + String queryStreamingFullMethodName = + QueryGrpc.getQueryStreamingMethod().getFullMethodName(); + + if (!queryFullMethodName.equals(fullMethodName) && + !queryStreamingFullMethodName.equals(fullMethodName)) { + return false; // Not a query request. + } + + Kvs.QueryRequest queryRequest = call.getRequestBuilder().getQueryRequest(); + if (queryRequest.getBackground()) { + return false; // Background queries send back a single response. + } + + if (queryRequest.getStatement().getMaxRecords() < LARGE_RESPONSE_CUTOFF) { + return false; // Records returned in responses is small. + } + + if (!queryRequest.getStatement().getFunctionName().isEmpty()) { + return false; // Query is an aggregation statement. + } + + if (queryRequest.hasQueryPolicy() && queryRequest.getQueryPolicy().getShortQuery()) { + return false; // Query is a short query. + } + + return true; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcCallExecutor.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcCallExecutor.java new file mode 100644 index 000000000..747942302 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcCallExecutor.java @@ -0,0 +1,292 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.io.Closeable; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.LongAdder; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nullable; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Host; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.client.proxy.auth.AuthTokenManager; +import com.aerospike.proxy.client.AboutGrpc; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.Deadline; +import io.grpc.ManagedChannel; +import io.grpc.stub.StreamObserver; +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; + +public class GrpcCallExecutor implements Closeable { + private static final int QUEUE_SIZE_UPPER_BOUND = 100 * 1024; + public static final int MIN_WARMUP_TIMEOUT = 5_000; + private final List channelExecutors; + private final List controlChannelExecutors; + private final GrpcClientPolicy grpcClientPolicy; + private final Random random = new Random(); + private final AtomicBoolean isClosed = new AtomicBoolean(false); + + /** + * Maximum allowed queue size. + */ + private final int maxQueueSize; + + private final LongAdder totalQueueSize = new LongAdder(); + private final GrpcChannelExecutor.ChannelTypeAndEventLoop controlChannelTypeAndEventLoop; + + public GrpcCallExecutor( + GrpcClientPolicy grpcClientPolicy, + @Nullable AuthTokenManager authTokenManager, + Host... hosts + ) { + if (hosts == null || hosts.length < 1) { + throw new AerospikeException(ResultCode.PARAMETER_ERROR, + "need at least one seed host"); + } + + this.grpcClientPolicy = grpcClientPolicy; + maxQueueSize = + Math.min(QUEUE_SIZE_UPPER_BOUND, + 5 * grpcClientPolicy.maxChannels + * grpcClientPolicy.maxConcurrentStreamsPerChannel + * grpcClientPolicy.maxConcurrentRequestsPerStream); + + this.controlChannelTypeAndEventLoop = getControlEventLoop(); + + try { + this.channelExecutors = + IntStream.range(0, grpcClientPolicy.maxChannels).mapToObj(value -> + new GrpcChannelExecutor(grpcClientPolicy, + new GrpcChannelExecutor.ChannelTypeAndEventLoop(grpcClientPolicy.channelType, + grpcClientPolicy.nextEventLoop()), + authTokenManager, hosts) + ).collect(Collectors.toList()); + this.controlChannelExecutors = + IntStream.range(0, 1).mapToObj(value -> + new GrpcChannelExecutor(grpcClientPolicy, + controlChannelTypeAndEventLoop, authTokenManager, + hosts) + ).collect(Collectors.toList()); + } + catch (Exception e) { + throw new AerospikeException(ResultCode.SERVER_ERROR, e); + } + } + + /** + * Warmup the channels with a call to the About gRPC endpoint. + */ + public void warmupChannels() { + final CountDownLatch doneSignal = + new CountDownLatch(channelExecutors.size()); + final int timeoutMillis = Math.max(MIN_WARMUP_TIMEOUT, + grpcClientPolicy.connectTimeoutMillis); + + channelExecutors.forEach(executor -> { + ManagedChannel channel = executor.getChannel(); + AboutGrpc.newStub(channel) + .withDeadline(Deadline.after(timeoutMillis, TimeUnit.MILLISECONDS)) + .get(Kvs.AboutRequest.newBuilder().build(), new StreamObserver() { + @Override + public void onNext(Kvs.AboutResponse value) { + doneSignal.countDown(); + } + + @Override + public void onError(Throwable t) { + Exception exception = new Exception("About call in warmup " + + "failed: ", t); + Log.debug(GrpcConversions.getDisplayMessage(exception + , GrpcConversions.MAX_ERR_MSG_LENGTH)); + doneSignal.countDown(); + } + + @Override + public void onCompleted() { + } + }); + }); + + try { + doneSignal.await(timeoutMillis, TimeUnit.MILLISECONDS); + } + catch (Throwable ignore) { + } + } + + public void execute(GrpcStreamingCall call) { + if (totalQueueSize.sum() > maxQueueSize) { + call.onError(new AerospikeException(ResultCode.NO_MORE_CONNECTIONS, + "Maximum queue " + maxQueueSize + " size exceeded")); + return; + } + + GrpcChannelExecutor executor = + grpcClientPolicy.grpcChannelSelector.select(channelExecutors, call); + + // TODO: In case of timeouts, lots of calls will end up filling the + // wait queues and timeout once removed for execution from the wait + // queue. Have a upper limit on the number of concurrent transactions + // per channel and reject this call if all the channels are full. + totalQueueSize.increment(); + + try { + executor.execute(new WrappedGrpcStreamingCall(call)); + } + catch (Exception e) { + // Call scheduling failed. + totalQueueSize.decrement(); + } + } + + public EventLoop getEventLoop() { + return channelExecutors.get(random.nextInt(channelExecutors.size())) + .getEventLoop(); + } + + public ManagedChannel getControlChannel() { + if (controlChannelExecutors.isEmpty()) { + return null; + } + return controlChannelExecutors.get(random.nextInt(controlChannelExecutors.size())) + .getChannel(); + } + + public ManagedChannel getChannel() { + if(channelExecutors.isEmpty()) { + return null; + } + return channelExecutors.get(random.nextInt(channelExecutors.size())) + .getChannel(); + } + + @Override + public void close() { + if (isClosed.getAndSet(true)) { + return; + } + + closeExecutors(channelExecutors); + closeExecutors(controlChannelExecutors); + + // Event loops should be closed after shutdown of channels. + closeEventLoops(); + } + + private GrpcChannelExecutor.ChannelTypeAndEventLoop getControlEventLoop() { + EventLoopGroup eventLoopGroup; + Class channelType; + DefaultThreadFactory tf = new DefaultThreadFactory("aerospike-proxy-control", true /*daemon*/); + + if (Epoll.isAvailable()) { + eventLoopGroup = new EpollEventLoopGroup(1, tf); + channelType = EpollSocketChannel.class; + } + else { + eventLoopGroup = new NioEventLoopGroup(1, tf); + channelType = NioSocketChannel.class; + } + + return new GrpcChannelExecutor.ChannelTypeAndEventLoop(channelType, (EventLoop)eventLoopGroup.iterator().next()); + } + + private void closeExecutors(List executors) { + for (GrpcChannelExecutor executor : executors) { + executor.shutdown(); + } + + // Wait for all executors to terminate. + while (true) { + boolean allTerminated = executors.stream() + .allMatch(GrpcChannelExecutor::isTerminated); + + if (allTerminated) { + return; + } + + Log.debug("Waiting for executors to shutdown with closeTimeout=" + grpcClientPolicy.closeTimeout); + try { + //noinspection BusyWait + Thread.sleep(1000); + } + catch (Throwable t) {/* Ignore*/} + } + } + + + private void closeEventLoops() { + if (grpcClientPolicy.closeEventLoops) { + closeEventLoops(grpcClientPolicy.eventLoops); + } + + // Close the control event loop. + closeEventLoops(Collections.singletonList(controlChannelTypeAndEventLoop.getEventLoop())); + } + + private void closeEventLoops(List eventLoops) { + eventLoops.stream() + .map(eventLoop -> + eventLoop.shutdownGracefully(0, grpcClientPolicy.terminationWaitMillis, TimeUnit.MILLISECONDS) + ).forEach(future -> { + try { + future.await(grpcClientPolicy.terminationWaitMillis); + } + catch (Exception e) { + // TODO log error? + } + } + ); + } + + private class WrappedGrpcStreamingCall extends GrpcStreamingCall { + WrappedGrpcStreamingCall(GrpcStreamingCall delegate) { + super(delegate); + } + + @Override + public void onNext(Kvs.AerospikeResponsePayload payload) { + if (!payload.getHasNext()) { + totalQueueSize.decrement(); + } + super.onNext(payload); + } + + @Override + public void onError(Throwable t) { + totalQueueSize.decrement(); + super.onError(t); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelExecutor.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelExecutor.java new file mode 100644 index 000000000..7a7873a69 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelExecutor.java @@ -0,0 +1,690 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.io.FileInputStream; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Host; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.client.policy.TlsPolicy; +import com.aerospike.client.proxy.AerospikeClientProxy; +import com.aerospike.client.proxy.auth.AuthTokenManager; +import com.aerospike.client.util.Util; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.CallOptions; +import io.grpc.ManagedChannel; +import io.grpc.MethodDescriptor; +import io.grpc.NameResolver; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NegotiationType; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoop; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.CipherSuiteFilter; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.util.concurrent.ScheduledFuture; +import io.netty.util.internal.shaded.org.jctools.queues.MpscUnboundedArrayQueue; + +/** + * All gRPC requests on a HTTP/2 channel are handled by this class throughout + * the channel lifetime. + *

+ * TODO: handle close of channel. + */ +public class GrpcChannelExecutor implements Runnable { + /** + * System property to configure gRPC override authority used as hostname + * in TLS verification of the proxy server. + */ + public static final String OVERRIDE_AUTHORITY = "com.aerospike.client" + + ".overrideAuthority"; + + private static final String AEROSPIKE_CLIENT_USER_AGENT = + "AerospikeClientJava/" + AerospikeClientProxy.Version; + + /** + * The delay between iterations of this executor. + *

+ * TODO: how to select interval of execution? + */ + private static final long ITERATION_DELAY_MICROS = 250; + + /** + * Unique executor ids. + */ + private static final AtomicLong executorIdIndex = new AtomicLong(); + private static final AtomicInteger streamIdIndex = new AtomicInteger(); + + /** + * The HTTP/2 channel of this executor. + */ + private final ManagedChannel channel; + /** + * The Aerospike gRPC client policy. + */ + private final GrpcClientPolicy grpcClientPolicy; + /** + * The auth token manager. + */ + private final AuthTokenManager authTokenManager; + /** + * The event loop bound to the channel. All queued requests + * will be executed on this event loop. Some requests will be queued on + * this channel in the gRPC callback and some from the pending queue. + */ + private final EventLoop eventLoop; + /** + * Queued unary calls awaiting execution. + */ + private final MpscUnboundedArrayQueue pendingCalls = + new MpscUnboundedArrayQueue<>(32); + /** + * Queue of closed streams. + */ + private final List closedStreams = new ArrayList<>(32); + /** + * Map of stream id to streams. + */ + private final Map streams = new HashMap<>(); + /** + * Shutdown initiation time. + */ + private long shutdownStartTimeNanos; + /** + * Current state of the channel. + */ + private final AtomicReference channelState; + /** + * Unique id of the executor. + */ + private final long id; + // Statistics. + private final AtomicLong ongoingRequests = new AtomicLong(); + private final int drainLimit; + + /** + * The future to cancel the scheduled iteration of this executor. + */ + private ScheduledFuture iterateFuture; + + /** + * Time when the channel executor saw an invalid token. If this field is + * zero the token is valid. + *

+ * Is not volatile because it is access from a single thread. + */ + private long tokenInvalidStartTime = 0; + + public GrpcChannelExecutor( + GrpcClientPolicy grpcClientPolicy, + ChannelTypeAndEventLoop channelTypeAndEventLoop, + @Nullable AuthTokenManager authTokenManager, + Host... hosts + ) { + if (grpcClientPolicy == null) { + throw new NullPointerException("grpcClientPolicy"); + } + if (hosts == null || hosts.length == 0) { + throw new IllegalArgumentException("hosts should be non-empty"); + } + + this.grpcClientPolicy = grpcClientPolicy; + this.drainLimit = + this.grpcClientPolicy.maxConcurrentStreamsPerChannel * grpcClientPolicy.maxConcurrentRequestsPerStream; + this.authTokenManager = authTokenManager; + this.id = executorIdIndex.getAndIncrement(); + + ChannelAndEventLoop channelAndEventLoop = + createGrpcChannel(channelTypeAndEventLoop.getEventLoop() + , channelTypeAndEventLoop.getChannelType(), hosts); + this.channel = channelAndEventLoop.managedChannel; + this.eventLoop = channelAndEventLoop.eventLoop; + + this.channelState = new AtomicReference<>(ChannelState.READY); + + this.iterateFuture = + channelAndEventLoop.eventLoop.scheduleAtFixedRate(this, 0, + ITERATION_DELAY_MICROS, TimeUnit.MICROSECONDS); + } + + private static SslContext getSslContext(TlsPolicy tlsPolicy) { + try { + SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); + Field field = sslContextBuilder.getClass().getDeclaredField("apn"); + field.setAccessible(true); + ApplicationProtocolConfig applicationProtocolConfig = (ApplicationProtocolConfig)field.get(sslContextBuilder); + + if (tlsPolicy.context != null) { + CipherSuiteFilter csf = (tlsPolicy.ciphers != null) ? (iterable, list, set) -> { + if (tlsPolicy.ciphers != null) { + return tlsPolicy.ciphers; + } + return tlsPolicy.context.getSupportedSSLParameters().getCipherSuites(); + } : IdentityCipherSuiteFilter.INSTANCE; + + // Enforce ALPN in case NPN_AND_ALPN is the supported protocol. + // JdkSslContext fails with an exception when the protocol is + // NPN_AND_ALPN. + ApplicationProtocolConfig apn = applicationProtocolConfig; + if (applicationProtocolConfig.protocol() == ApplicationProtocolConfig.Protocol.NPN_AND_ALPN) { + // Constructor copied verbatim from package-private field + // io.grpc.netty.GrpcSslContexts.ALPN + apn = new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + Collections.singletonList("h2")); + } + + return new JdkSslContext(tlsPolicy.context, true, null, csf, apn, ClientAuth.NONE, null, false); + } + + SslContextBuilder builder = SslContextBuilder.forClient(); + builder.applicationProtocolConfig(applicationProtocolConfig); + if (tlsPolicy.protocols != null) { + builder.protocols(tlsPolicy.protocols); + } + + if (tlsPolicy.ciphers != null) { + builder.ciphers(Arrays.asList(tlsPolicy.ciphers)); + } + + String keyStoreLocation = System.getProperty("javax.net.ssl.keyStore"); + + // Keystore is only required for mutual authentication. + if (keyStoreLocation != null) { + String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); + char[] pass = (keyStorePassword != null) ? keyStorePassword.toCharArray() : null; + + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + + try (FileInputStream is = new FileInputStream(keyStoreLocation)) { + ks.load(is, pass); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, pass); + + builder.keyManager(kmf); + } + return builder.build(); + } + catch (Exception e) { + throw new AerospikeException("Failed to init netty TLS: " + Util.getErrorMessage(e)); + } + } + + /** + * Create a gRPC channel. + */ + @SuppressWarnings("deprecation") + private ChannelAndEventLoop createGrpcChannel(EventLoop eventLoop, Class channelType, Host[] hosts) { + NettyChannelBuilder builder; + + if (hosts.length == 1) { + builder = NettyChannelBuilder.forAddress(hosts[0].name, hosts[0].port); + } + else { + // Setup round-robin load balancing. + NameResolver.Factory nameResolverFactory = new MultiAddressNameResolverFactory( + Arrays.stream(hosts) + .map((host) -> new InetSocketAddress(host.name, host.port)) + .collect( + Collectors.toList())); + builder = NettyChannelBuilder.forTarget(String.format("%s:%d", + hosts[0].name, hosts[0].port)); + builder.nameResolverFactory(nameResolverFactory); + builder.defaultLoadBalancingPolicy("round_robin"); + } + + SingleEventLoopGroup eventLoopGroup = new SingleEventLoopGroup(eventLoop); + builder + .eventLoopGroup(eventLoopGroup) + .perRpcBufferLimit(128 * 1024 * 1024) + .channelType(channelType) + .negotiationType(NegotiationType.PLAINTEXT) + + // Have a very large limit because this response is coming from + // the proxy server. + .maxInboundMessageSize(128 * 1024 * 1024) + + // Execute callbacks in the assigned event loop. + // GrpcChannelExecutor.iterate and all of GrpcStream works on + // this assumption. + .directExecutor() + + // Retry logic is part of the client code. + .disableRetry() + + // Server and client flow control policy should be in sync. + .flowControlWindow(2 * 1024 * 1024) + + // TODO: is this beneficial? See https://github.com/grpc/grpc-java/issues/8260 + // for discussion. + // Enabling this feature create too many pings and the server + // sends GO_AWAY response. + // .initialFlowControlWindow(1024 * 1024) + + // TODO: Should these be part of GrpcClientPolicy? + .keepAliveWithoutCalls(true) + .keepAliveTime(25, TimeUnit.SECONDS) + .keepAliveTimeout(1, TimeUnit.MINUTES); + + if (grpcClientPolicy.tlsPolicy != null) { + builder.sslContext(getSslContext(grpcClientPolicy.tlsPolicy)); + builder.negotiationType(NegotiationType.TLS); + } + else { + builder.usePlaintext(); + } + + // For testing. Set this to force a hostname irrespective of the + // target IP for TLS verification. A simpler way than adding a DNS + // entry in the hosts file. + String authorityProperty = System.getProperty(OVERRIDE_AUTHORITY); + + if (authorityProperty != null && !authorityProperty.trim().isEmpty()) { + builder.overrideAuthority(authorityProperty); + } + + //setting buffer size can improve I/O + builder.withOption(ChannelOption.SO_SNDBUF, 1048576); + builder.withOption(ChannelOption.SO_RCVBUF, 1048576); + builder.withOption(ChannelOption.TCP_NODELAY, true); + builder.withOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, + grpcClientPolicy.connectTimeoutMillis); + builder.userAgent(AEROSPIKE_CLIENT_USER_AGENT); + + // TODO: better to have a receive buffer predictor + //builder.withOption(ChannelOption.valueOf("receiveBufferSizePredictorFactory"), new AdaptiveReceiveBufferSizePredictorFactory(MIN_PACKET_SIZE, INITIAL_PACKET_SIZE, MAX_PACKET_SIZE)) + + //if the server is sending 1000 messages per sec, optimum write buffer watermarks will + //prevent unnecessary throttling, Check NioSocketChannelConfig doc + builder.withOption(ChannelOption.WRITE_BUFFER_WATER_MARK, + new WriteBufferWaterMark(32 * 1024, 64 * 1024)); + + ManagedChannel channel = builder.build(); + // TODO: ensure it is a single threaded event loop. + return new ChannelAndEventLoop(channel, eventLoop); + } + + public void execute(GrpcStreamingCall call) { + if (channelState.get() != ChannelState.READY) { + call.failIfNotComplete(ResultCode.CLIENT_ERROR); + return; + } + // TODO: add always succeeds? + ongoingRequests.getAndIncrement(); + pendingCalls.add(call); + } + + @Override + public void run() { + try { + iterate(); + } + catch (Exception e) { + // TODO: signal failure, close channel? + if (Log.debugEnabled()) { + Log.debug("Uncaught exception in " + this + ":" + e); + } + } + } + + /** + * Process a single iteration. + */ + private void iterate() { + switch (channelState.get()) { + case READY: + executeCalls(); + break; + + case SHUTTING_DOWN: + boolean allCallsCompleted = pendingCalls.isEmpty() && + streams.values().stream() + .allMatch(grpcStream -> grpcStream.getOngoingRequests() == 0); + + int closeTimeout = grpcClientPolicy.closeTimeout; + if (closeTimeout < 0) { + // Shutdown immediately. + shutdownNow(); + } + else if (closeTimeout == 0) { + // Wait for all pending calls to complete. + if (allCallsCompleted) { + shutdownNow(); + } + else { + Log.debug(this + " shutdown: awaiting completion of " + + "all calls for closeTimeout=0."); + executeCalls(); + } + } + else { + // Wait for all pending calls to complete or timeout. + long elapsedTimeMillis = + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - shutdownStartTimeNanos); + if (allCallsCompleted || elapsedTimeMillis >= closeTimeout) { + shutdownNow(); + } + else { + Log.debug(this + " shutdown: awaiting closeTimeout=" + + closeTimeout + ", elapsed time=" + elapsedTimeMillis); + executeCalls(); + } + } + break; + + case SHUTDOWN: + Log.warn("Iterate being called after channel shutdown"); + break; + + default: + Log.error("Unknown channel state: " + channelState.get()); + break; + } + } + + private void executeCalls() { + if (authTokenManager != null) { + AuthTokenManager.TokenStatus tokenStatus = + authTokenManager.getTokenStatus(); + if (!tokenStatus.isValid()) { + expireOrDrainOnInvalidToken(tokenStatus.getError()); + return; + } + } + + // Schedule pending calls onto streams. + pendingCalls.drain(this::scheduleCalls, drainLimit); + + // Execute stream calls. + streams.values().forEach(GrpcStream::executePendingCalls); + + // Process closed streams. + closedStreams.forEach(this::processClosedStream); + closedStreams.clear(); + } + + /** + * Expire queued calls and drain queue if required when we have an invalid + * auth token. + */ + private void expireOrDrainOnInvalidToken(Throwable tokenError) { + assert authTokenManager != null; + + if (tokenInvalidStartTime == 0) { + tokenInvalidStartTime = System.currentTimeMillis(); + } + + // Token is invalid. This happens at the start before the first + // access token fetch or if the token expires and could not be + // refreshed. + pendingCalls.forEach(call -> { + if (!call.hasCompleted() && + (call.hasSendDeadlineExpired() || call.hasExpired())) { + call.onError(tokenError); + } + }); + + + long tokenWaitTimeout = tokenInvalidStartTime + authTokenManager.getRefreshMinTime() * 3L; + + if (tokenWaitTimeout < System.currentTimeMillis()) { + tokenInvalidStartTime = 0; + // It's been too long without a valid access token. Drain and + // report all queued calls as failed. + pendingCalls.drain(call -> call.failIfNotComplete(tokenError)); + } + } + + /** + * Schedule the call on a stream. + */ + private void scheduleCalls(GrpcStreamingCall call) { + if (call.hasCompleted()) { + // Most likely expired while in queue. + return; + } + + if (call.hasSendDeadlineExpired() || call.hasExpired()) { + call.onError(new AerospikeException.Timeout(call.getPolicy(), + call.getIteration())); + return; + } + + // The stream will be close by the selector. + GrpcStreamSelector.SelectedStream selectedStream = + grpcClientPolicy.grpcStreamSelector.select(new ArrayList<>(streams.values()), call); + + if (selectedStream == null) { + // Requeue + pendingCalls.add(call); + return; + } + + if (selectedStream.useExistingStream()) { + selectedStream.getStream().enqueue(call); + return; + } + + scheduleCallsOnNewStream(call.getStreamingMethodDescriptor(), call, + selectedStream.getMaxConcurrentRequestsPerStream(), + selectedStream.getTotalRequestsPerStream()); + } + + private void processClosedStream(GrpcStream grpcStream) { + if (streams.remove(grpcStream.getId()) == null) { + // Should never happen. + return; + } + + // Schedule each of the pending calls. + pendingCalls.addAll(grpcStream.getPendingCalls()); + } + + /** + * Schedule calls in pendingCalls on a new stream. + */ + private void scheduleCallsOnNewStream( + MethodDescriptor methodDescriptor, + GrpcStreamingCall call, + int maxConcurrentRequestsPerStream, int totalRequestsPerStream + ) { + if (maxConcurrentRequestsPerStream <= 0) { // Should never happen. + maxConcurrentRequestsPerStream = + grpcClientPolicy.maxConcurrentRequestsPerStream; + } + if (totalRequestsPerStream <= 0) { // Should never happen. + totalRequestsPerStream = grpcClientPolicy.totalRequestsPerStream; + } + + CallOptions options = grpcClientPolicy.callOptions; + if (authTokenManager != null) { + try { + options = authTokenManager.setCallCredentials(grpcClientPolicy.callOptions); + } + catch (Exception e) { + AerospikeException aerospikeException = + new AerospikeException(ResultCode.NOT_AUTHENTICATED, e); + call.onError(aerospikeException); + return; + } + } + + LinkedList streamPendingCalls = new LinkedList<>(); + streamPendingCalls.add(call); + GrpcStream stream = new GrpcStream(this, methodDescriptor, + streamPendingCalls, options, nextStreamId(), eventLoop, + maxConcurrentRequestsPerStream, totalRequestsPerStream); + + streams.put(stream.getId(), stream); + } + + /** + * Start the shutdown of this channel. Any new requests will be rejected. + * The shutdown respects the clientTimeout setting. Use + * {@link #isTerminated()} to see if shutdown is complete. + */ + public void shutdown() { + if (!channelState.compareAndSet(ChannelState.READY, ChannelState.SHUTTING_DOWN)) { + return; + } + + shutdownStartTimeNanos = System.nanoTime(); + + // If inside event loop thread, cannot wait for calls in this channel + // to complete without deadlocking, abort and shutdown now. + if (eventLoop.inEventLoop()) { + shutdownNow(); + } + } + + /** + * WARN This method should always be called from the [eventLoop] + * thread. + */ + private void shutdownNow() { + if (channelState.getAndSet(ChannelState.SHUTDOWN) == ChannelState.SHUTDOWN) { + return; + } + + closeAllPendingCalls(); + channel.shutdownNow(); + iterateFuture.cancel(false); + } + + private void closeAllPendingCalls() { + while (!pendingCalls.isEmpty()) { + pendingCalls.drain(call -> { + try { + call.failIfNotComplete(ResultCode.CLIENT_ERROR); + } + catch (Exception e) { + Log.error("Error on call close " + call + ": " + e.getMessage()); + } + }); + } + streams.values().forEach(stream -> { + try { + stream.closePendingCalls(); + } + catch (Exception e) { + Log.error("Error closing stream " + stream + ": " + e.getMessage()); + } + }); + streams.clear(); + } + + boolean isTerminated() { + return channelState.get() == ChannelState.SHUTDOWN && channel.isTerminated(); + } + + private int nextStreamId() { + return streamIdIndex.getAndIncrement(); + } + + @Override + public String toString() { + return "GrpcChannelExecutor{id=" + id + '}'; + } + + public long getId() { + return id; + } + + public long getOngoingRequests() { + return ongoingRequests.get(); + } + + void onRequestCompleted() { + ongoingRequests.getAndDecrement(); + } + + public void onStreamClosed(GrpcStream grpcStream) { + closedStreams.add(grpcStream); + } + + public ManagedChannel getChannel() { + return channel; + } + + public EventLoop getEventLoop() { + return eventLoop; + } + + private static class ChannelAndEventLoop { + final ManagedChannel managedChannel; + final EventLoop eventLoop; + + private ChannelAndEventLoop(ManagedChannel managedChannel, EventLoop eventLoop) { + this.managedChannel = managedChannel; + this.eventLoop = eventLoop; + } + } + + private enum ChannelState { + READY, SHUTTING_DOWN, SHUTDOWN + } + + public static class ChannelTypeAndEventLoop { + private final Class channelType; + private final EventLoop eventLoop; + + public ChannelTypeAndEventLoop(Class channelType, EventLoop eventLoop) { + this.channelType = channelType; + this.eventLoop = eventLoop; + } + + public Class getChannelType() { + return channelType; + } + + public EventLoop getEventLoop() { + return eventLoop; + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelProvider.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelProvider.java new file mode 100644 index 000000000..7d6890f4c --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import io.grpc.ManagedChannel; + +public class GrpcChannelProvider { + private GrpcCallExecutor callExecutor; + + /** + * @return a managed channel if available else null. + */ + public ManagedChannel getControlChannel() { + if (callExecutor == null) { + return null; + } + return callExecutor.getControlChannel(); + } + + public void setCallExecutor(GrpcCallExecutor callExecutor) { + this.callExecutor = callExecutor; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelSelector.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelSelector.java new file mode 100644 index 000000000..6384147d2 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcChannelSelector.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.List; + +/** + * A selector of channels to execute Aerospike proxy gRPC calls. + */ +public interface GrpcChannelSelector { + /** + * Select a channel for the gRPC method. + * + * @param channels channels to select from. + * @param call the streaming call to be executed. + * @return the selected channel. + */ + GrpcChannelExecutor select(List channels, GrpcStreamingCall call); +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcClientPolicy.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcClientPolicy.java new file mode 100644 index 000000000..f41d3eb46 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcClientPolicy.java @@ -0,0 +1,383 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import javax.annotation.Nullable; + +import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.policy.TlsPolicy; + +import io.grpc.CallOptions; +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; + +/** + * gRPC Aerospike proxy client policy. All the knobs and configs that + * affect the working of the gRPC proxy client are combined into this policy + * object. + */ +public class GrpcClientPolicy { + /** + * The event loops to process the gRPC HTTP/2 requests. + */ + public final List eventLoops; + + /** + * The type of the eventLoops. + */ + public final Class channelType; + + /** + * Should the event loops be closed in close. + */ + public final boolean closeEventLoops; + + /** + * Maximum number of HTTP/2 channels (connections) to open to the Aerospike + * gRPC proxy server. + *

+ * Generally HTTP/2 based gRPC recommends a single channel to be + * sufficient for most purposes. In our performance experiments we have + * found that any number greater than 8 yields no extra + * performance gains. + */ + public final int maxChannels; + + /** + * Maximum number of concurrent HTTP/2 streams to have in-flight per HTTP/2 + * channel (connection). + *

+ * Generally HTTP/2 servers restrict the number of concurrent HTTP/2 streams + * to about a 100 on a channel (connection). + */ + public final int maxConcurrentStreamsPerChannel; + + /** + * Maximum number of concurrent requests that are in-flight per streaming + * HTTP/2 call. + *

+ * The Aerospike gRPC proxy server implements streaming for unary calls + * like Aerospike get, put, operate, etc to improve latency and throughput. + * maxConcurrentRequestsPerStream specifies the number of + * concurrent requests that can be sent on a single unary call based stream. + *

+ * NOTE This policy does not apply to queries, scans, etc. + */ + public final int maxConcurrentRequestsPerStream; + + /** + * Total number of HTTP/2 requests that are sent on a stream, after which + * the stream is closed. + *

+ * The Aerospike gRPC proxy server implements streaming for unary calls + * like Aerospike get, put, operate, etc to improve latency and throughput. + * totalRequestsPerStream specifies the total number of + * requests that are sent on the stream, after which the stream is closed. + *

+ * Requests to the Aerospike gRPC proxy server will be routed through a + * HTTP/2 load balancer over the public internet. HTTP/2 load balancer + * splits requests on a single HTTP/2 channel (connection) across the proxy + * servers, but it will send all requests on a HTTP/2 stream to a single + * gRPC Aerospike proxy server. This policy ensures that the requests are + * evenly load balanced across the gRPC Aerospike proxy servers. + *

+ * NOTE This policy does not apply to queries, scans, etc. + */ + public final int totalRequestsPerStream; + + /** + * The connection timeout in milliseconds when creating a new HTTP/2 + * channel (connection) to a gRPC Aerospike proxy server. + */ + public final int connectTimeoutMillis; + + /** + * See {@link ClientPolicy#closeTimeout}. + */ + public final int closeTimeout; + + /** + * Strategy to select a channel for a gRPC request. + */ + + public final GrpcChannelSelector grpcChannelSelector; + + /** + * Strategy to select a stream for a gRPC request. + */ + public final GrpcStreamSelector grpcStreamSelector; + + /** + * Call options. + */ + public final CallOptions callOptions; + + /** + * The TLS policy to connect to the gRPC Aerospike proxy server. + *

+ * NOTE The channel (connection) will be non-encrypted if + * this policy is null. + */ + @Nullable + public final TlsPolicy tlsPolicy; + + /** + * Milliseconds to wait for termination of the channels. Should be + * greater than the deadlines. The implementation is best-effort, its + * possible termination takes more time than this. + */ + public final long terminationWaitMillis; + + /** + * Index to get the next event loop. + */ + private final AtomicInteger eventLoopIndex = new AtomicInteger(0); + + private GrpcClientPolicy( + int maxChannels, + int maxConcurrentStreamsPerChannel, + int maxConcurrentRequestsPerStream, + int totalRequestsPerStream, + int connectTimeoutMillis, + long terminationWaitMillis, + int closeTimeout, GrpcChannelSelector grpcChannelSelector, + GrpcStreamSelector grpcStreamSelector, + CallOptions callOptions, + List eventLoops, + Class channelType, + boolean closeEventLoops, + @Nullable TlsPolicy tlsPolicy + ) { + this.maxChannels = maxChannels; + this.maxConcurrentStreamsPerChannel = maxConcurrentStreamsPerChannel; + this.maxConcurrentRequestsPerStream = maxConcurrentRequestsPerStream; + this.totalRequestsPerStream = totalRequestsPerStream; + this.connectTimeoutMillis = connectTimeoutMillis; + this.terminationWaitMillis = terminationWaitMillis; + this.closeTimeout = closeTimeout; + this.grpcChannelSelector = grpcChannelSelector; + this.grpcStreamSelector = grpcStreamSelector; + this.callOptions = callOptions; + this.eventLoops = eventLoops; + this.channelType = channelType; + this.closeEventLoops = closeEventLoops; + this.tlsPolicy = tlsPolicy; + } + + public static Builder newBuilder( + @Nullable List eventLoops, + @Nullable Class channelType + ) { + Builder builder = new Builder(); + + if (eventLoops == null || channelType == null) { + builder.closeEventLoops = true; + + DefaultThreadFactory tf = + new DefaultThreadFactory("aerospike-proxy", true /*daemon */); + + // TODO: select number of event loop threads? + EventLoopGroup eventLoopGroup; + + if (Epoll.isAvailable()) { + eventLoopGroup = new EpollEventLoopGroup(0, tf); + builder.channelType = EpollSocketChannel.class; + } + else { + eventLoopGroup = new NioEventLoopGroup(0, tf); + builder.channelType = NioSocketChannel.class; + } + + builder.eventLoops = StreamSupport.stream(eventLoopGroup.spliterator(), false) + .map(eventExecutor -> (EventLoop)eventExecutor) + .collect(Collectors.toList()); + } + else { + builder.channelType = channelType; + builder.eventLoops = eventLoops; + builder.closeEventLoops = false; + } + + // TODO: justify defaults. + builder.maxChannels = 8; + + // Multiple requests should be sent on a stream at once to enhance + // performance. So `maxConcurrentRequestsPerStream` should be the + // ideal batch size of the requests. + // TODO: maybe these parameters depend on the payload size? + builder.maxConcurrentStreamsPerChannel = 8; + builder.maxConcurrentRequestsPerStream = builder.totalRequestsPerStream = 128; + + builder.connectTimeoutMillis = 5000; + builder.terminationWaitMillis = 30000; + + builder.tlsPolicy = null; + return builder; + } + + public EventLoop nextEventLoop() { + int i = eventLoopIndex.getAndIncrement() % eventLoops.size(); + return eventLoops.get(i); + } + + public static class Builder { + private List eventLoops; + private Class channelType; + private boolean closeEventLoops; + private int maxChannels; + private int maxConcurrentStreamsPerChannel; + private int maxConcurrentRequestsPerStream; + private int totalRequestsPerStream; + private int connectTimeoutMillis; + @Nullable + private TlsPolicy tlsPolicy; + @Nullable + private GrpcChannelSelector grpcChannelSelector; + @Nullable + private GrpcStreamSelector grpcStreamSelector; + @Nullable + private CallOptions callOptions; + private long terminationWaitMillis; + private int closeTimeout; + + private Builder() { + } + + public GrpcClientPolicy build() { + if (grpcChannelSelector == null) { + // TODO: how should low and high water mark be selected? + int hwm = + maxConcurrentStreamsPerChannel * maxConcurrentRequestsPerStream; + int lwm = Math.max(16, (int)(0.8 * hwm)); + grpcChannelSelector = + new DefaultGrpcChannelSelector(lwm, hwm); + } + + if (grpcStreamSelector == null) { + grpcStreamSelector = + new DefaultGrpcStreamSelector(maxConcurrentStreamsPerChannel, maxConcurrentRequestsPerStream, totalRequestsPerStream); + } + + if (callOptions == null) { + callOptions = CallOptions.DEFAULT; + } + + return new GrpcClientPolicy(maxChannels, maxConcurrentStreamsPerChannel, + maxConcurrentRequestsPerStream, totalRequestsPerStream, + connectTimeoutMillis, terminationWaitMillis, closeTimeout, + grpcChannelSelector, grpcStreamSelector, callOptions, eventLoops, + channelType, closeEventLoops, tlsPolicy); + } + + public Builder maxChannels(int maxChannels) { + if (maxChannels < 1) { + throw new IllegalArgumentException(String.format( + "maxChannels=%d < 1", maxChannels + )); + } + this.maxChannels = maxChannels; + return this; + } + + public Builder maxConcurrentStreamsPerChannel(int maxConcurrentStreamsPerChannel) { + if (maxConcurrentStreamsPerChannel < 1) { + throw new IllegalArgumentException(String.format( + "maxConcurrentStreamsPerChannel=%d < 1", maxConcurrentStreamsPerChannel + )); + } + this.maxConcurrentStreamsPerChannel = maxConcurrentStreamsPerChannel; + return this; + } + + public Builder maxConcurrentRequestsPerStream(int maxConcurrentRequestsPerStream) { + if (maxConcurrentRequestsPerStream < 1) { + throw new IllegalArgumentException(String.format( + "maxConcurrentRequestsPerStream=%d < 1", maxConcurrentRequestsPerStream + )); + } + this.maxConcurrentRequestsPerStream = maxConcurrentRequestsPerStream; + return this; + } + + public Builder totalRequestsPerStream(int totalRequestsPerStream) { + if (totalRequestsPerStream < 0) { + throw new IllegalArgumentException(String.format( + "totalRequestsPerStream=%d < 0", totalRequestsPerStream + )); + } + this.totalRequestsPerStream = totalRequestsPerStream; + return this; + } + + public Builder connectTimeoutMillis(int connectTimeoutMillis) { + if (connectTimeoutMillis < 0) { + throw new IllegalArgumentException(String.format( + "connectTimeoutMillis=%d < 0", connectTimeoutMillis + )); + } + this.connectTimeoutMillis = connectTimeoutMillis; + return this; + } + + public Builder closeTimeout(int closeTimeout) { + this.closeTimeout = closeTimeout; + return this; + } + + public Builder tlsPolicy(@Nullable TlsPolicy tlsPolicy) { + this.tlsPolicy = tlsPolicy; + return this; + } + + public Builder grpcChannelSelector(GrpcChannelSelector grpcChannelSelector) { + this.grpcChannelSelector = grpcChannelSelector; + return this; + } + + public Builder grpcStreamSelector(GrpcStreamSelector grpcStreamSelector) { + this.grpcStreamSelector = grpcStreamSelector; + return this; + } + + public Builder callOptions(@Nullable CallOptions callOptions) { + this.callOptions = callOptions; + return this; + } + + public Builder terminationWaitMillis(long terminationWaitMillis) { + if (terminationWaitMillis < 0) { + throw new IllegalArgumentException(String.format( + "terminationWaitMillis=%d < 0", terminationWaitMillis + )); + } + this.terminationWaitMillis = terminationWaitMillis; + return this; + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcConversions.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcConversions.java new file mode 100644 index 000000000..06300d622 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcConversions.java @@ -0,0 +1,423 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Operation; +import com.aerospike.client.ResultCode; +import com.aerospike.client.Value; +import com.aerospike.client.policy.Policy; +import com.aerospike.client.policy.QueryPolicy; +import com.aerospike.client.policy.ScanPolicy; +import com.aerospike.client.policy.WritePolicy; +import com.aerospike.client.query.Filter; +import com.aerospike.client.query.PartitionFilter; +import com.aerospike.client.query.PartitionStatus; +import com.aerospike.client.query.Statement; +import com.aerospike.client.util.Packer; +import com.aerospike.proxy.client.Kvs; +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; + +/** + * Conversions from native client objects to Grpc objects. + */ +public class GrpcConversions { + private static final String ERROR_MESSAGE_SEPARATOR = " -> "; + public static final int MAX_ERR_MSG_LENGTH = 10 * 1024; + + public static void setRequestPolicy( + Policy policy, + Kvs.AerospikeRequestPayload.Builder requestBuilder + ) { + if (policy instanceof WritePolicy) { + Kvs.WritePolicy.Builder writePolicyBuilder = Kvs.WritePolicy.newBuilder(); + + Kvs.ReadModeAP readModeAP = Kvs.ReadModeAP.valueOf(policy.readModeAP.name()); + writePolicyBuilder.setReadModeAP(readModeAP); + + Kvs.ReadModeSC readModeSC = Kvs.ReadModeSC.valueOf(policy.readModeSC.name()); + writePolicyBuilder.setReadModeSC(readModeSC); + + Kvs.Replica replica = Kvs.Replica.valueOf(policy.replica.name()); + writePolicyBuilder.setReplica(replica); + + requestBuilder.setWritePolicy(writePolicyBuilder.build()); + } + else { + Kvs.ReadPolicy.Builder readPolicyBuilder = Kvs.ReadPolicy.newBuilder(); + + Kvs.ReadModeAP readModeAP = Kvs.ReadModeAP.valueOf(policy.readModeAP.name()); + readPolicyBuilder.setReadModeAP(readModeAP); + + Kvs.ReadModeSC readModeSC = Kvs.ReadModeSC.valueOf(policy.readModeSC.name()); + readPolicyBuilder.setReadModeSC(readModeSC); + + Kvs.Replica replica = Kvs.Replica.valueOf(policy.replica.name()); + readPolicyBuilder.setReplica(replica); + + requestBuilder.setReadPolicy(readPolicyBuilder.build()); + } + } + + public static Kvs.ScanPolicy toGrpc(ScanPolicy scanPolicy) { + // Base policy fields. + Kvs.ScanPolicy.Builder scanPolicyBuilder = Kvs.ScanPolicy.newBuilder(); + + Kvs.ReadModeAP readModeAP = Kvs.ReadModeAP.valueOf(scanPolicy.readModeAP.name()); + scanPolicyBuilder.setReadModeAP(readModeAP); + + Kvs.ReadModeSC readModeSC = Kvs.ReadModeSC.valueOf(scanPolicy.readModeSC.name()); + scanPolicyBuilder.setReadModeSC(readModeSC); + + Kvs.Replica replica = Kvs.Replica.valueOf(scanPolicy.replica.name()); + scanPolicyBuilder.setReplica(replica); + + if (scanPolicy.filterExp != null) { + scanPolicyBuilder.setExpression(ByteString.copyFrom(scanPolicy.filterExp.getBytes())); + } + + scanPolicyBuilder.setTotalTimeout(scanPolicy.totalTimeout); + scanPolicyBuilder.setCompress(scanPolicy.compress); + + // Scan policy specific fields + scanPolicyBuilder.setMaxRecords(scanPolicy.maxRecords); + scanPolicyBuilder.setRecordsPerSecond(scanPolicy.recordsPerSecond); + scanPolicyBuilder.setMaxConcurrentNodes(scanPolicy.maxConcurrentNodes); + scanPolicyBuilder.setConcurrentNodes(scanPolicy.concurrentNodes); + scanPolicyBuilder.setIncludeBinData(scanPolicy.includeBinData); + return scanPolicyBuilder.build(); + } + + public static Kvs.QueryPolicy toGrpc(QueryPolicy queryPolicy) { + // Base policy fields. + Kvs.QueryPolicy.Builder queryPolicyBuilder = Kvs.QueryPolicy.newBuilder(); + + Kvs.ReadModeAP readModeAP = Kvs.ReadModeAP.valueOf(queryPolicy.readModeAP.name()); + queryPolicyBuilder.setReadModeAP(readModeAP); + + Kvs.ReadModeSC readModeSC = Kvs.ReadModeSC.valueOf(queryPolicy.readModeSC.name()); + queryPolicyBuilder.setReadModeSC(readModeSC); + + Kvs.Replica replica = Kvs.Replica.valueOf(queryPolicy.replica.name()); + queryPolicyBuilder.setReplica(replica); + + if (queryPolicy.filterExp != null) { + queryPolicyBuilder.setExpression(ByteString.copyFrom(queryPolicy.filterExp.getBytes())); + } + + queryPolicyBuilder.setTotalTimeout(queryPolicy.totalTimeout); + queryPolicyBuilder.setCompress(queryPolicy.compress); + queryPolicyBuilder.setSendKey(queryPolicy.sendKey); + + // Query policy specific fields + queryPolicyBuilder.setMaxConcurrentNodes(queryPolicy.maxConcurrentNodes); + queryPolicyBuilder.setRecordQueueSize(queryPolicy.recordQueueSize); + queryPolicyBuilder.setInfoTimeout(queryPolicy.infoTimeout); + queryPolicyBuilder.setIncludeBinData(queryPolicy.includeBinData); + queryPolicyBuilder.setFailOnClusterChange(queryPolicy.failOnClusterChange); + queryPolicyBuilder.setShortQuery(queryPolicy.shortQuery); + return queryPolicyBuilder.build(); + } + + /** + * Convert a value to packed bytes. + * + * @param value the value to pack + * @return the packed bytes. + */ + public static ByteString valueToByteString(Value value) { + // TODO: @Brian is there a better way to convert value to bytes? + // This involves two copies. One when returning bytes Packer + // and one for the byte string. + Packer packer = new Packer(); + value.pack(packer); + return ByteString.copyFrom(packer.toByteArray()); + } + + public static Kvs.Filter toGrpc(Filter filter) { + Kvs.Filter.Builder builder = Kvs.Filter.newBuilder(); + + builder.setName(filter.getName()); + builder.setValType(filter.getValType()); + + if (filter.getBegin() != null) { + // TODO: @Brian is there a better way to convert value to bytes? + // This involves two copies. One when returning bytes Packer + // and one for the byte string. + Packer packer = new Packer(); + filter.getBegin().pack(packer); + builder.setBegin(ByteString.copyFrom(packer.toByteArray())); + } + + if (filter.getBegin() != null) { + builder.setBegin(valueToByteString(filter.getBegin())); + } + + if (filter.getEnd() != null) { + builder.setEnd(valueToByteString(filter.getEnd())); + } + + if (filter.getPackedCtx() != null) { + builder.setPackedCtx(ByteString.copyFrom(filter.getPackedCtx())); + } + + builder.setColType(Kvs.IndexCollectionType.valueOf(filter.getColType().name())); + return builder.build(); + } + + public static Kvs.Operation toGrpc(Operation operation) { + Kvs.Operation.Builder builder = Kvs.Operation.newBuilder(); + builder.setType(Kvs.OperationType.valueOf(operation.type.name())); + + if (operation.binName != null) { + builder.setBinName(operation.binName); + } + + if (operation.value != null) { + builder.setValue(valueToByteString(operation.value)); + } + return builder.build(); + } + + /** + * @param statement Aerospike client statement + * @param taskId required non-zero taskId to use for the execution at the proxy + * @param maxRecords max records to return + * @return equivalent gRPC {@link com.aerospike.proxy.client.Kvs.Statement} + */ + public static Kvs.Statement toGrpc(Statement statement, long taskId, long maxRecords) { + Kvs.Statement.Builder statementBuilder = Kvs.Statement.newBuilder(); + statementBuilder.setNamespace(statement.getNamespace()); + + if (statement.getSetName() != null) { + statementBuilder.setSetName(statement.getSetName()); + } + + if (statement.getIndexName() != null) { + statementBuilder.setIndexName(statement.getIndexName()); + } + + if (statement.getBinNames() != null) { + for (String binName : statement.getBinNames()) { + statementBuilder.addBinNames(binName); + } + } + + if (statement.getFilter() != null) { + statementBuilder.setFilter(toGrpc(statement.getFilter())); + } + + + if (statement.getPackageName() != null) { + statementBuilder.setPackageName(statement.getPackageName()); + } + + if (statement.getFunctionName() != null) { + statementBuilder.setFunctionName(statement.getFunctionName()); + } + + if (statement.getFunctionArgs() != null) { + for (Value arg : statement.getFunctionArgs()) { + statementBuilder.addFunctionArgs(valueToByteString(arg)); + } + } + + if (statement.getOperations() != null) { + for (Operation operation : statement.getOperations()) { + statementBuilder.addOperations(toGrpc(operation)); + } + } + + statementBuilder.setTaskId(taskId); + + statementBuilder.setMaxRecords(maxRecords); + statementBuilder.setRecordsPerSecond(statement.getRecordsPerSecond()); + return statementBuilder.build(); + } + + public static Kvs.PartitionStatus toGrpc(PartitionStatus ps) { + Kvs.PartitionStatus.Builder builder = Kvs.PartitionStatus.newBuilder(); + builder.setId(ps.id); + builder.setBVal(ps.bval); + builder.setRetry(ps.retry); + if (ps.digest != null) { + builder.setDigest(ByteString.copyFrom(ps.digest)); + } + return builder.build(); + } + + public static Kvs.PartitionFilter toGrpc(PartitionFilter partitionFilter) { + Kvs.PartitionFilter.Builder builder = Kvs.PartitionFilter.newBuilder(); + builder.setBegin(partitionFilter.getBegin()); + builder.setCount(partitionFilter.getCount()); + builder.setRetry(partitionFilter.isRetry()); + + byte[] digest = partitionFilter.getDigest(); + if (digest != null && digest.length > 0) { + builder.setDigest(ByteString.copyFrom(digest)); + } + + if (partitionFilter.getPartitions() != null) { + for (PartitionStatus ps : partitionFilter.getPartitions()) { + builder.addPartitionStatuses(toGrpc(ps)); + } + } + return builder.build(); + } + + public static Kvs.BackgroundExecutePolicy toGrpc(WritePolicy writePolicy) { + // Base policy fields. + Kvs.BackgroundExecutePolicy.Builder queryPolicyBuilder = Kvs.BackgroundExecutePolicy.newBuilder(); + + Kvs.ReadModeAP readModeAP = Kvs.ReadModeAP.valueOf(writePolicy.readModeAP.name()); + queryPolicyBuilder.setReadModeAP(readModeAP); + + Kvs.ReadModeSC readModeSC = Kvs.ReadModeSC.valueOf(writePolicy.readModeSC.name()); + queryPolicyBuilder.setReadModeSC(readModeSC); + + Kvs.Replica replica = Kvs.Replica.valueOf(writePolicy.replica.name()); + queryPolicyBuilder.setReplica(replica); + + if (writePolicy.filterExp != null) { + queryPolicyBuilder.setExpression(ByteString.copyFrom(writePolicy.filterExp.getBytes())); + } + + queryPolicyBuilder.setTotalTimeout(writePolicy.totalTimeout); + queryPolicyBuilder.setCompress(writePolicy.compress); + queryPolicyBuilder.setSendKey(writePolicy.sendKey); + + // Query policy specific fields + queryPolicyBuilder.setRecordExistsAction(Kvs.RecordExistsAction.valueOf(writePolicy.recordExistsAction.name())); + queryPolicyBuilder.setGenerationPolicy(Kvs.GenerationPolicy.valueOf(writePolicy.generationPolicy.name())); + queryPolicyBuilder.setCommitLevel(Kvs.CommitLevel.valueOf(writePolicy.commitLevel.name())); + queryPolicyBuilder.setGeneration(writePolicy.generation); + queryPolicyBuilder.setExpiration(writePolicy.expiration); + queryPolicyBuilder.setRespondAllOps(writePolicy.respondAllOps); + queryPolicyBuilder.setDurableDelete(writePolicy.durableDelete); + queryPolicyBuilder.setXdr(writePolicy.xdr); + return queryPolicyBuilder.build(); + } + + public static AerospikeException toAerospike(StatusRuntimeException sre, Policy policy, int iteration) { + Status.Code code = sre.getStatus().getCode(); + int resultCode = ResultCode.CLIENT_ERROR; + switch (code) { + case CANCELLED: + case UNKNOWN: + case NOT_FOUND: + case ALREADY_EXISTS: + case FAILED_PRECONDITION: + case OUT_OF_RANGE: + case UNIMPLEMENTED: + case INTERNAL: + resultCode = ResultCode.CLIENT_ERROR; + break; + + case ABORTED: + case DATA_LOSS: + resultCode = ResultCode.SERVER_ERROR; + break; + + case INVALID_ARGUMENT: + resultCode = ResultCode.SERIALIZE_ERROR; + break; + + case DEADLINE_EXCEEDED: + return new AerospikeException.Timeout(policy, iteration); + + case PERMISSION_DENIED: + resultCode = ResultCode.FAIL_FORBIDDEN; + break; + + case RESOURCE_EXHAUSTED: + resultCode = ResultCode.QUOTA_EXCEEDED; + break; + + case UNAUTHENTICATED: + resultCode = ResultCode.NOT_AUTHENTICATED; + break; + + case UNAVAILABLE: + resultCode = ResultCode.SERVER_NOT_AVAILABLE; + break; + + case OK: + resultCode = ResultCode.OK; + break; + } + + return new AerospikeException(resultCode, getDisplayMessage(sre, MAX_ERR_MSG_LENGTH), sre); + } + + /** + * Get the error message to display restricting it to some length. + */ + public static String getDisplayMessage(Throwable e, int maxMsgLength) { + if (maxMsgLength <= 0) { + return ""; + } + + String errorMessage = getMessage(e); + Throwable rootCause = e.getCause(); + while (rootCause != null) { + String current = getMessage(rootCause); + errorMessage = (errorMessage.isEmpty()) ? current + : errorMessage + ERROR_MESSAGE_SEPARATOR + current; + rootCause = rootCause.getCause(); + } + + return take(errorMessage, maxMsgLength); + } + + /** + * Take at most first `n` characters from the string. + * + * @param s input string + * @param n number of characters to take. + * @return the string that is at most `n` characters in length. + */ + private static String take(String s, int n) { + int trimLength = Math.min(n, s.length()); + if (trimLength <= 0) { + return ""; + } + return s.substring(0, trimLength); + } + + /** + * Get error message for [e]. + */ + private static String getMessage(Throwable e) { + if (e == null) { + return ""; + } + + String errorMessage = e.getMessage() != null ? e.getMessage() : ""; + + errorMessage = errorMessage.split("\\r?\\n|\\r")[0]; + if (errorMessage.trim().isEmpty()) { + return e.getClass().getName(); + } + else { + return String.format("%s - %s", e.getClass().getName(), + errorMessage); + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcStream.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStream.java new file mode 100644 index 000000000..69029c532 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStream.java @@ -0,0 +1,473 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.Log; +import com.aerospike.client.ResultCode; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.CallOptions; +import io.grpc.ClientCall; +import io.grpc.MethodDescriptor; +import io.grpc.stub.ClientCalls; +import io.grpc.stub.StreamObserver; +import io.netty.channel.EventLoop; + +/** + * This class executes a single Aerospike API method like get, put, etc. + * throughout its lifetime. It executes a maximum of `totalRequestsPerStream` + * before closing the stream. + *

+ * NOTE All methods of the stream are executed within a single + * thread. This is implemented by + *

    + *
  • having the channel configured to use the direct executor
  • + *
  • have the channel and streams associated with the channel be + * executed on a single event loop
  • + *
+ *

+ */ +public class GrpcStream implements StreamObserver { + /** + * Idle timeout after which the stream is closed, when no call are pending. + */ + private static final long IDLE_TIMEOUT = 30_000; + + /** + * Unique stream id in the channel. + */ + private final int id; + + /** + * The event loop within which all of GrpcStream calls are executed. + */ + private final EventLoop eventLoop; + + /** + * The request observer of the stream. + */ + private StreamObserver requestObserver; + + /** + * Maximum number of concurrent requests that can be in-flight. + */ + private final int maxConcurrentRequests; + + /** + * Total number of requests to process in this stream for its lifetime. + */ + private final int totalRequestsToExecute; + + /** + * The executor for this stream. + */ + private final GrpcChannelExecutor channelExecutor; + + /** + * The method processed by this stream. + */ + private final MethodDescriptor methodDescriptor; + + /** + * Queued calls pending execution. + *

+ * WARN Ensure this is always accessed from the {@link #eventLoop} + * thread. + */ + private final LinkedList pendingCalls; + + /** + * Map of request id to the calls executing in this stream. + */ + private final Map executingCalls = new HashMap<>(); + + /** + * Is the stream closed. This variable is only accessed from the event + * loop thread assigned to this stream and its channel. + */ + private boolean isClosed = false; + + // Stream statistics. These are only updated from the event loop thread + // assigned to this stream and its channel. + + /** + * Number of requests sent on the gRPC stream. This is only updated from + * the event loop thread assigned to this stream and its channel. + */ + private volatile int requestsSent; + + /** + * Number of requests completed. This is only updated from + * the event loop thread assigned to this stream and its channel. + */ + private volatile int requestsCompleted; + + /** + * Timer started when this stream has no pending calls. + * There may still be calls executing (in-flight). + */ + private volatile long streamIdleStartTime; + + /** + * Indicates if the gRPC stream has been half closed from this side. + */ + private boolean streamHalfClosed; + + public GrpcStream( + GrpcChannelExecutor channelExecutor, + MethodDescriptor methodDescriptor, + LinkedList pendingCalls, + CallOptions callOptions, + int streamIndex, + EventLoop eventLoop, + int maxConcurrentRequests, + int totalRequestsToExecute + ) { + this.channelExecutor = channelExecutor; + this.methodDescriptor = methodDescriptor; + this.pendingCalls = pendingCalls; + this.id = streamIndex; + this.eventLoop = eventLoop; + this.maxConcurrentRequests = maxConcurrentRequests; + this.totalRequestsToExecute = totalRequestsToExecute; + ClientCall call = channelExecutor.getChannel() + .newCall(methodDescriptor, callOptions); + StreamObserver requestObserver = + ClientCalls.asyncBidiStreamingCall(call, this); + setRequestObserver(requestObserver); + } + + private void setRequestObserver(StreamObserver requestObserver) { + this.requestObserver = requestObserver; + } + + @Override + public void onNext(Kvs.AerospikeResponsePayload aerospikeResponsePayload) { + if (!eventLoop.inEventLoop()) { + // This call is not within the event loop thread. For some reason + // gRPC invokes some callbacks from a different thread. + eventLoop.schedule(() -> onNext(aerospikeResponsePayload), 0, + TimeUnit.NANOSECONDS); + return; + } + + // Invoke callback. + int callId = aerospikeResponsePayload.getId(); + GrpcStreamingCall call; + + if (aerospikeResponsePayload.getHasNext()) { + call = executingCalls.get(callId); + } + else { + call = executingCalls.remove(callId); + + // Update stats. + requestsCompleted++; + channelExecutor.onRequestCompleted(); + } + + // Call might have expired and been cancelled. + if (call != null && !call.isAborted()) { + try { + call.onNext(aerospikeResponsePayload); + } + catch (Throwable t) { + if (aerospikeResponsePayload.getHasNext()) { + abortCallAtServer(call, callId); + } + } + } + + executePendingCalls(); + } + + private void abortCallAtServer(GrpcStreamingCall call, int callId) { + call.markAborted(); + + // Let the proxy know that there has been a failure so that + // it can abort long-running jobs. + int requestId = requestsSent++; + Kvs.AerospikeRequestPayload.Builder builder = Kvs.AerospikeRequestPayload.newBuilder(); + builder.setId(requestId); + builder.setAbortRequest(Kvs.AbortRequest.newBuilder().setAbortId(callId)); + requestObserver.onNext(builder.build()); + } + + private void abortExecutingCalls(Throwable throwable) { + isClosed = true; + + for (GrpcStreamingCall call : executingCalls.values()) { + try { + call.onError(throwable); + } + catch (Exception e) { + Log.debug("Exception in invoking onError: " + e); + } + } + + markClosed(); + } + + /** + * Marks the stream as closed and moves pending calls nack to the channel + * executor. + */ + private void markClosed() { + isClosed = true; + + executingCalls.clear(); + + channelExecutor.onStreamClosed(this); + } + + @Override + public void onError(Throwable throwable) { + if (!eventLoop.inEventLoop()) { + // This call is not within the event loop thread. For some reason + // gRPC invokes error callback from a different thread. + eventLoop.schedule(() -> onError(throwable), 0, + TimeUnit.NANOSECONDS); + return; + } + + if (executingCalls.isEmpty()) { + // gRPC stream creation failed. + // Fail all pending calls, otherwise they will keep cycling + // through until a stream creation succeeds. + for (GrpcStreamingCall call : pendingCalls) { + try { + call.onError(throwable); + } + catch (Exception e) { + Log.debug("Exception in invoking onError: " + e); + } + } + pendingCalls.clear(); + } + + abortExecutingCalls(throwable); + } + + @Override + public void onCompleted() { + if (!eventLoop.inEventLoop()) { + eventLoop.schedule(this::onCompleted, 0, TimeUnit.NANOSECONDS); + return; + } + + abortExecutingCalls(new AerospikeException(ResultCode.SERVER_ERROR, + "stream completed before all responses have been received")); + } + + LinkedList getPendingCalls() { + return pendingCalls; + } + + MethodDescriptor getMethodDescriptor() { + return methodDescriptor; + } + + int getOngoingRequests() { + return executingCalls.size() + pendingCalls.size(); + } + + public int getId() { + return id; + } + + public int getRequestsCompleted() { + return requestsCompleted; + } + + int getMaxConcurrentRequests() { + return maxConcurrentRequests; + } + + int getTotalRequestsToExecute() { + return totalRequestsToExecute; + } + + @Override + public String toString() { + return "GrpcStream{id=" + id + ", channelExecutor=" + channelExecutor + '}'; + } + + public int getExecutedRequests() { + return getRequestsCompleted() + getOngoingRequests(); + } + + public void executePendingCalls() { + if (isClosed) { + return; + } + + if (pendingCalls.isEmpty()) { + if (streamIdleStartTime == 0) { + streamIdleStartTime = System.currentTimeMillis(); + } + + if (streamIdleStartTime + IDLE_TIMEOUT <= System.currentTimeMillis() && !streamHalfClosed) { + streamHalfClosed = true; + requestObserver.onCompleted(); + } + } + else if (streamIdleStartTime != 0) { + streamIdleStartTime = 0; + } + + if (streamHalfClosed) { + if (executingCalls.isEmpty()) { + // All executing calls are over. + markClosed(); + } + + // Should not push any new calls on this stream. + return; + } + + Iterator iterator = pendingCalls.iterator(); + while (iterator.hasNext()) { + GrpcStreamingCall call = iterator.next(); + + if (call.hasSendDeadlineExpired() || call.hasExpired()) { + call.onError(new AerospikeException.Timeout(call.getPolicy(), + call.getIteration())); + + iterator.remove(); // Remove from pending. + } + else if (executingCalls.size() < maxConcurrentRequests && requestsSent < totalRequestsToExecute) { + execute(call); + iterator.remove(); // Remove from pending. + } + else { + // Call remains in pending. + } + } + } + + private void execute(GrpcStreamingCall call) { + try { + if (call.hasExpired()) { + call.onError(new AerospikeException.Timeout(call.getPolicy(), + call.getIteration())); + return; + } + + Kvs.AerospikeRequestPayload.Builder requestBuilder = call.getRequestBuilder(); + + int requestId = requestsSent++; + requestBuilder + .setId(requestId) + .setIteration(call.getIteration()); + + GrpcConversions.setRequestPolicy(call.getPolicy(), requestBuilder); + Kvs.AerospikeRequestPayload requestPayload = requestBuilder + .build(); + executingCalls.put(requestId, call); + + requestObserver.onNext(requestPayload); + + if (requestsSent >= totalRequestsToExecute) { + // Complete this stream. + requestObserver.onCompleted(); + streamHalfClosed = true; + } + + if (call.hasExpiry()) { + // TODO: Is there a need for a more efficient implementation in + // terms of the call cancellation. + eventLoop.schedule(() -> onCallExpired(requestId), + call.nanosTillExpiry(), TimeUnit.NANOSECONDS); + } + } + catch (Exception e) { + // Failure in scheduling or delegating through request observer. + call.onError(e); + } + } + + private void onCallExpired(int callId) { + GrpcStreamingCall call = executingCalls.remove(callId); + + // Call has completed. + if (call == null) { + return; + } + + // Cancel call. + call.onError(new AerospikeException.Timeout(call.getPolicy(), call.getIteration())); + + // Abort long-running calls at server. + if (!call.isSingleResponse()) { + abortCallAtServer(call, callId); + } + } + + boolean canEnqueue() { + return !isClosed && !streamHalfClosed && requestsSent < totalRequestsToExecute; + } + + /** + * Enqueue the call to this stream. Should only be invoked if + * {@link #canEnqueue()} returned true. + */ + void enqueue(GrpcStreamingCall call) { + pendingCalls.add(call); + } + + public void closePendingCalls() { + pendingCalls.forEach(call -> { + try { + call.failIfNotComplete(ResultCode.CLIENT_ERROR); + } + catch (Exception e) { + Log.error("Error shutting down " + this.getClass() + ": " + e.getMessage()); + } + }); + + pendingCalls.clear(); + + executingCalls.values().forEach(call -> { + try { + call.failIfNotComplete(ResultCode.CLIENT_ERROR); + } + catch (Exception e) { + Log.error("Error shutting down " + this.getClass() + ": " + e.getMessage()); + } + }); + + executingCalls.clear(); + + // For hygiene complete the stream as well. + try { + requestObserver.onCompleted(); + streamHalfClosed =true; + } + catch (Throwable t) { + // Ignore. + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamSelector.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamSelector.java new file mode 100644 index 000000000..69e684fe5 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamSelector.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.List; + +/** + * A selector of streams within a channel to execute Aerospike proxy gRPC calls. + */ +public interface GrpcStreamSelector { + /** + * Select a stream for the gRPC method. All streams created by the + * selector should be close when the selector is closed. + * + * @param streams streams to select from. + * @param call the streaming call to be executed. + * @return the selected stream, null when no stream is + * selected. + */ + SelectedStream select(List streams, GrpcStreamingCall call); + + + class SelectedStream { + /** + * Wil be non-null only when a current stream is selected. + */ + private final GrpcStream stream; + + // Following fields only applies when {@link #stream} is + // null + + private final int maxConcurrentRequestsPerStream; + private final int totalRequestsPerStream; + + /** + * Create a new stream with the supplied parameters. + */ + public SelectedStream(int maxConcurrentRequestsPerStream, int totalRequestsPerStream) { + this.stream = null; + this.maxConcurrentRequestsPerStream = maxConcurrentRequestsPerStream; + this.totalRequestsPerStream = totalRequestsPerStream; + } + + /** + * Use an existing stream. + */ + public SelectedStream(GrpcStream stream) { + this.stream = stream; + this.maxConcurrentRequestsPerStream = 0; + this.totalRequestsPerStream = 0; + } + + boolean useExistingStream() { + return stream != null; + } + + public GrpcStream getStream() { + return stream; + } + + public int getMaxConcurrentRequestsPerStream() { + return maxConcurrentRequestsPerStream; + } + + public int getTotalRequestsPerStream() { + return totalRequestsPerStream; + } + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamingCall.java b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamingCall.java new file mode 100644 index 000000000..9c7d14391 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/GrpcStreamingCall.java @@ -0,0 +1,217 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import com.aerospike.client.AerospikeException; +import com.aerospike.client.policy.Policy; +import com.aerospike.proxy.client.Kvs; + +import io.grpc.MethodDescriptor; +import io.grpc.stub.StreamObserver; + +/** + * A gRPC call that is converted to a streaming call for performance. + */ +public class GrpcStreamingCall { + /** + * The streaming method to execute for this unary call. + */ + private final MethodDescriptor methodDescriptor; + + /** + * The request builder populated with command call specific parameters. + */ + private final Kvs.AerospikeRequestPayload.Builder requestBuilder; + + /** + * The stream response observer for the call. + */ + private final StreamObserver responseObserver; + + /** + * The deadline in nanoseconds w.r.t System.nanoTime() for this call to + * complete. A value of zero indicates that the call has no deadline. + */ + private final long deadlineNanos; + + /** + * The deadline in nanoseconds w.r.t System.nanoTime() for this call to + * be handed to the underlying gRPC sub system. A value of zero indicates + * that the call has no send deadline. + */ + private final long sendDeadlineNanos; + + /** + * Aerospike client policy for this request. + */ + private final Policy policy; + + /** + * Iteration number of this request. + */ + private final int iteration; + + /** + * Number of expected responses for this request. A negative value + * indicates that the number of responses is unknown. + */ + private final int numExpectedResponses; + + /** + * Indicates if this call completed (successfully or unsuccessfully). + */ + private volatile boolean completed; + + /** + * Indicates if this call aborted due to an application exception.. + */ + private volatile boolean aborted; + + protected GrpcStreamingCall(GrpcStreamingCall other) { + this(other.methodDescriptor, other.requestBuilder, other.getPolicy(), + other.iteration, other.deadlineNanos, other.sendDeadlineNanos, + other.numExpectedResponses, other.responseObserver); + + completed = other.completed; + aborted = other.aborted; + } + + public GrpcStreamingCall( + MethodDescriptor methodDescriptor, + Kvs.AerospikeRequestPayload.Builder requestBuilder, + Policy policy, + int iteration, + long deadlineNanos, + long sendDeadlineNanos, + int numExpectedResponses, + StreamObserver responseObserver + ) { + this.responseObserver = responseObserver; + this.methodDescriptor = methodDescriptor; + this.requestBuilder = requestBuilder; + this.iteration = iteration; + this.policy = policy; + this.deadlineNanos = deadlineNanos; + this.sendDeadlineNanos = sendDeadlineNanos; + this.numExpectedResponses = numExpectedResponses; + } + + public void onNext(Kvs.AerospikeResponsePayload payload) { + responseObserver.onNext(payload); + + if (!payload.getHasNext()) { + completed = true; + responseObserver.onCompleted(); + } + } + + public void onError(Throwable t) { + completed = true; + responseObserver.onError(t); + } + + /** + * Fail the call if it is not completed. + * + * @param resultCode aerospike error code. + */ + public void failIfNotComplete(int resultCode) { + if (!hasCompleted()) { + onError(new AerospikeException(resultCode)); + } + } + + /** + * Fail the call if it is not completed. + * + * @param throwable cause of failure. + */ + public void failIfNotComplete(Throwable throwable) { + if (!hasCompleted()) { + onError(throwable); + } + } + + /** + * @return true if this call has completed either because + * {@link #onNext(Kvs.AerospikeResponsePayload)} or + * {@link #onError(Throwable)} was invoked. + */ + public boolean hasCompleted() { + return completed; + } + + public MethodDescriptor getStreamingMethodDescriptor() { + return methodDescriptor; + } + + /** + * @return true if this call has expired. + */ + public boolean hasExpired() { + return hasExpiry() && (System.nanoTime() - deadlineNanos) >= 0; + } + + /** + * @return true if the send deadline has expired. + */ + public boolean hasSendDeadlineExpired() { + return sendDeadlineNanos > 0 && (System.nanoTime() - sendDeadlineNanos) >= 0; + } + + public boolean hasExpiry() { + return deadlineNanos != 0; + } + + public long nanosTillExpiry() { + if (!hasExpiry()) { + throw new IllegalStateException("call does not expire"); + } + long nanosTillExpiry = deadlineNanos - System.nanoTime(); + return nanosTillExpiry > 0 ? nanosTillExpiry : 0; + } + + public Kvs.AerospikeRequestPayload.Builder getRequestBuilder() { + return requestBuilder; + } + + public int getIteration() { + return iteration; + } + + public Policy getPolicy() { + return policy; + } + + public void markAborted() { + this.aborted = true; + this.completed = true; + } + + public boolean isAborted() { + return aborted; + } + + public boolean isSingleResponse() { + return numExpectedResponses == 1; + } + + public int getNumExpectedResponses() { + return numExpectedResponses; + } +} diff --git a/proxy/src/com/aerospike/client/proxy/grpc/MultiAddressNameResolverFactory.java b/proxy/src/com/aerospike/client/proxy/grpc/MultiAddressNameResolverFactory.java new file mode 100644 index 000000000..72603212e --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/MultiAddressNameResolverFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.net.SocketAddress; +import java.net.URI; +import java.util.List; +import java.util.stream.Collectors; + +import io.grpc.Attributes; +import io.grpc.EquivalentAddressGroup; +import io.grpc.NameResolver; + +class MultiAddressNameResolverFactory extends NameResolver.Factory { + + final List addresses; + + MultiAddressNameResolverFactory(List addresses) { + this.addresses = addresses.stream() + .map(EquivalentAddressGroup::new) + .collect(Collectors.toList()); + } + + public NameResolver newNameResolver(URI notUsedUri, NameResolver.Args args) { + return new NameResolver() { + @Override + public String getServiceAuthority() { + return "Authority"; + } + + public void start(Listener2 listener) { + listener.onResult(ResolutionResult.newBuilder().setAddresses(addresses) + .setAttributes(Attributes.EMPTY).build()); + } + + public void shutdown() { + } + }; + } + + @Override + public String getDefaultScheme() { + return "multiaddress"; + } +} \ No newline at end of file diff --git a/proxy/src/com/aerospike/client/proxy/grpc/SingleEventLoopGroup.java b/proxy/src/com/aerospike/client/proxy/grpc/SingleEventLoopGroup.java new file mode 100644 index 000000000..2832ec281 --- /dev/null +++ b/proxy/src/com/aerospike/client/proxy/grpc/SingleEventLoopGroup.java @@ -0,0 +1,188 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.client.proxy.grpc; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nonnull; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import com.fasterxml.jackson.databind.util.ArrayIterator; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ScheduledFuture; + +/** + * An event loop group containing a single event loop. + *

+ * TODO: verify it is correct to delegate to the singleton event loop. + */ +class SingleEventLoopGroup implements EventLoopGroup { + private final EventLoop eventLoop; + + SingleEventLoopGroup(EventLoop eventLoop) { + this.eventLoop = eventLoop; + } + + @Override + public boolean isShuttingDown() { + return eventLoop.isShuttingDown(); + } + + @Override + public Future shutdownGracefully() { + return eventLoop.shutdownGracefully(); + } + + @Override + public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + return eventLoop.shutdownGracefully(quietPeriod, timeout, unit); + } + + @Override + public Future terminationFuture() { + return eventLoop.terminationFuture(); + } + + @SuppressWarnings("deprecation") + @Override + public void shutdown() { + eventLoop.shutdown(); + } + + @SuppressWarnings("deprecation") + @Override + public List shutdownNow() { + return eventLoop.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return eventLoop.isShutdown(); + } + + @Override + public boolean isTerminated() { + return eventLoop.isShutdown(); + } + + @Override + public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) throws InterruptedException { + return eventLoop.awaitTermination(timeout, unit); + } + + @Override + public EventLoop next() { + return eventLoop; + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(new EventExecutor[]{eventLoop}); + } + + @Override + public Future submit(Runnable task) { + return eventLoop.submit(task); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks) throws InterruptedException { + return eventLoop.invokeAll(tasks); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException { + return eventLoop.invokeAll(tasks, timeout, unit); + } + + @NonNull + @Override + public T invokeAny(@NonNull Collection> tasks) throws InterruptedException, ExecutionException { + return eventLoop.invokeAny(tasks); + } + + @Override + public T invokeAny(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return eventLoop.invokeAny(tasks, timeout, unit); + } + + @Override + public Future submit(Runnable task, T result) { + return eventLoop.submit(task, result); + } + + @Override + public Future submit(Callable task) { + return eventLoop.submit(task); + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return eventLoop.schedule(command, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return eventLoop.schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return eventLoop.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return eventLoop.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @Override + public ChannelFuture register(io.netty.channel.Channel channel) { + return eventLoop.register(channel); + } + + @Override + public ChannelFuture register(ChannelPromise promise) { + return eventLoop.register(promise); + } + + @SuppressWarnings("deprecation") + @Override + public ChannelFuture register(io.netty.channel.Channel channel, ChannelPromise promise) { + return eventLoop.register(channel, promise); + } + + @Override + public void execute(@NonNull Runnable command) { + eventLoop.execute(command); + } +} diff --git a/test/pom.xml b/test/pom.xml index 985a78194..c6168f81f 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -24,6 +24,11 @@ aerospike-client + + com.aerospike + aerospike-proxy-client + + io.netty netty-transport @@ -57,13 +62,13 @@ commons-cli - + junit junit test - + ${project.basedir}/src @@ -86,10 +91,10 @@ make-my-jar-with-dependencies - package - - single - + package + + single + @@ -103,7 +108,7 @@ - + diff --git a/test/src/com/aerospike/test/SuiteAsync.java b/test/src/com/aerospike/test/SuiteAsync.java index 0c8669d62..8088dfee7 100644 --- a/test/src/com/aerospike/test/SuiteAsync.java +++ b/test/src/com/aerospike/test/SuiteAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -21,15 +21,17 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Log; import com.aerospike.client.async.EventLoop; +import com.aerospike.client.async.EventLoopType; import com.aerospike.client.async.EventLoops; import com.aerospike.client.async.EventPolicy; import com.aerospike.client.async.NettyEventLoops; import com.aerospike.client.async.NioEventLoops; import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.proxy.AerospikeClientFactory; import com.aerospike.test.async.TestAsyncBatch; import com.aerospike.test.async.TestAsyncOperate; import com.aerospike.test.async.TestAsyncPutGet; @@ -39,6 +41,7 @@ import com.aerospike.test.util.Args; import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; @@ -54,7 +57,7 @@ TestAsyncUDF.class }) public class SuiteAsync { - public static AerospikeClient client = null; + public static IAerospikeClient client = null; public static EventLoops eventLoops = null; public static EventLoop eventLoop = null; @@ -67,6 +70,16 @@ public static void init() { EventPolicy eventPolicy = new EventPolicy(); + if (args.useProxyClient && args.eventLoopType == EventLoopType.DIRECT_NIO) { + // Proxy client requires netty event loops. + if (Epoll.isAvailable()) { + args.eventLoopType = EventLoopType.NETTY_EPOLL; + } + else { + args.eventLoopType = EventLoopType.NETTY_NIO; + } + } + switch (args.eventLoopType) { default: case DIRECT_NIO: { @@ -101,16 +114,14 @@ public static void init() { try { ClientPolicy policy = new ClientPolicy(); + args.setClientPolicy(policy); policy.eventLoops = eventLoops; - policy.user = args.user; - policy.password = args.password; - policy.authMode = args.authMode; - policy.tlsPolicy = args.tlsPolicy; Host[] hosts = Host.parseHosts(args.host, args.port); eventLoop = eventLoops.get(0); - client = new AerospikeClient(policy, hosts); + + client = AerospikeClientFactory.getClient(policy, args.useProxyClient, hosts); try { args.setServerSpecific(client); diff --git a/test/src/com/aerospike/test/SuiteProxy.java b/test/src/com/aerospike/test/SuiteProxy.java new file mode 100644 index 000000000..2e5657ea0 --- /dev/null +++ b/test/src/com/aerospike/test/SuiteProxy.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012-2023 Aerospike, Inc. + * + * Portions may be licensed to Aerospike, Inc. under one or more contributor + * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. + * + * Licensed 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 com.aerospike.test; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import com.aerospike.test.async.TestAsyncBatch; +import com.aerospike.test.async.TestAsyncOperate; +import com.aerospike.test.async.TestAsyncPutGet; +import com.aerospike.test.async.TestAsyncScan; +import com.aerospike.test.sync.basic.TestAdd; +import com.aerospike.test.sync.basic.TestAppend; +import com.aerospike.test.sync.basic.TestBatch; +import com.aerospike.test.sync.basic.TestBitExp; +import com.aerospike.test.sync.basic.TestDeleteBin; +import com.aerospike.test.sync.basic.TestExpOperation; +import com.aerospike.test.sync.basic.TestExpire; +import com.aerospike.test.sync.basic.TestFilterExp; +import com.aerospike.test.sync.basic.TestGeneration; +import com.aerospike.test.sync.basic.TestHLLExp; +import com.aerospike.test.sync.basic.TestListExp; +import com.aerospike.test.sync.basic.TestListMap; +import com.aerospike.test.sync.basic.TestMapExp; +import com.aerospike.test.sync.basic.TestOperate; +import com.aerospike.test.sync.basic.TestOperateBit; +import com.aerospike.test.sync.basic.TestOperateHll; +import com.aerospike.test.sync.basic.TestOperateList; +import com.aerospike.test.sync.basic.TestOperateMap; +import com.aerospike.test.sync.basic.TestPutGet; +import com.aerospike.test.sync.basic.TestReplace; +import com.aerospike.test.sync.basic.TestScan; +import com.aerospike.test.sync.basic.TestTouch; +import com.aerospike.test.util.Args; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TestAsyncPutGet.class, + TestAsyncBatch.class, + TestAsyncOperate.class, + TestAsyncScan.class, + TestAdd.class, + TestAppend.class, + TestBatch.class, + TestBitExp.class, + TestDeleteBin.class, + TestExpire.class, + TestExpOperation.class, + TestFilterExp.class, + TestGeneration.class, + TestHLLExp.class, + TestListExp.class, + TestListMap.class, + TestMapExp.class, + TestOperate.class, + TestOperateBit.class, + TestOperateHll.class, + TestOperateList.class, + TestOperateMap.class, + TestPutGet.class, + TestReplace.class, + TestScan.class, + TestTouch.class +}) +public class SuiteProxy { + @BeforeClass + public static void init() { + Args args = Args.Instance; + args.useProxyClient = true; + + if (args.port == 3000) { + System.out.println("Change proxy server port to 4000"); + args.port = 4000; + } + + SuiteSync.init(); + SuiteAsync.init(); + } + + @AfterClass + public static void destroy() { + SuiteSync.destroy(); + SuiteAsync.destroy(); + } +} diff --git a/test/src/com/aerospike/test/SuiteSync.java b/test/src/com/aerospike/test/SuiteSync.java index 4585cfe3f..9c317e53f 100644 --- a/test/src/com/aerospike/test/SuiteSync.java +++ b/test/src/com/aerospike/test/SuiteSync.java @@ -21,10 +21,11 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Host; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Log; import com.aerospike.client.policy.ClientPolicy; +import com.aerospike.client.proxy.AerospikeClientFactory; import com.aerospike.test.sync.basic.TestAdd; import com.aerospike.test.sync.basic.TestAppend; import com.aerospike.test.sync.basic.TestBatch; @@ -107,7 +108,7 @@ TestQuerySum.class }) public class SuiteSync { - public static AerospikeClient client = null; + public static IAerospikeClient client = null; @BeforeClass public static void init() { @@ -117,14 +118,11 @@ public static void init() { Args args = Args.Instance; ClientPolicy policy = new ClientPolicy(); - policy.user = args.user; - policy.password = args.password; - policy.authMode = args.authMode; - policy.tlsPolicy = args.tlsPolicy; + args.setClientPolicy(policy); Host[] hosts = Host.parseHosts(args.host, args.port); - client = new AerospikeClient(policy, hosts); + client = AerospikeClientFactory.getClient(policy, args.useProxyClient, hosts); try { args.setServerSpecific(client); diff --git a/test/src/com/aerospike/test/async/TestAsync.java b/test/src/com/aerospike/test/async/TestAsync.java index 77344c69b..4b7299dbd 100644 --- a/test/src/com/aerospike/test/async/TestAsync.java +++ b/test/src/com/aerospike/test/async/TestAsync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -19,8 +19,8 @@ import org.junit.AfterClass; import org.junit.BeforeClass; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.async.EventLoop; @@ -29,7 +29,7 @@ import com.aerospike.test.util.TestBase; public class TestAsync extends TestBase { - protected static AerospikeClient client = SuiteAsync.client; + protected static IAerospikeClient client = SuiteAsync.client; protected static EventLoop eventLoop = SuiteAsync.eventLoop; private static boolean DestroyClient = false; diff --git a/test/src/com/aerospike/test/async/TestAsyncPutGet.java b/test/src/com/aerospike/test/async/TestAsyncPutGet.java index 8de8e595a..3f0a99c7a 100644 --- a/test/src/com/aerospike/test/async/TestAsyncPutGet.java +++ b/test/src/com/aerospike/test/async/TestAsyncPutGet.java @@ -21,9 +21,9 @@ import org.junit.Test; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.listener.RecordListener; @@ -79,13 +79,13 @@ public void asyncPutGetWithRetry() { } private class WriteHandler implements WriteListener { - private final AerospikeClient client; + private final IAerospikeClient client; private final WritePolicy policy; private final Key key; private final Bin bin; private int failCount = 0; - public WriteHandler(AerospikeClient client, WritePolicy policy, Key key, Bin bin) { + public WriteHandler(IAerospikeClient client, WritePolicy policy, Key key, Bin bin) { this.client = client; this.policy = policy; this.key = key; @@ -127,13 +127,13 @@ public void onFailure(AerospikeException e) { } private class ReadHandler implements RecordListener { - private final AerospikeClient client; + private final IAerospikeClient client; private final Policy policy; private final Key key; private final Bin bin; private int failCount = 0; - public ReadHandler(AerospikeClient client, Policy policy, Key key, Bin bin) { + public ReadHandler(IAerospikeClient client, Policy policy, Key key, Bin bin) { this.client = client; this.policy = policy; this.key = key; diff --git a/test/src/com/aerospike/test/async/TestAsyncUDF.java b/test/src/com/aerospike/test/async/TestAsyncUDF.java index 5b2f3b137..a5c75d108 100644 --- a/test/src/com/aerospike/test/async/TestAsyncUDF.java +++ b/test/src/com/aerospike/test/async/TestAsyncUDF.java @@ -41,6 +41,10 @@ public class TestAsyncUDF extends TestAsync { @BeforeClass public static void prepare() { + if (args.useProxyClient) { + System.out.println("Skip TestAsyncUDF.prepare"); + return; + } RegisterTask rtask = client.register(null, TestAsyncUDF.class.getClassLoader(), "udf/record_example.lua", "record_example.lua", Language.LUA); rtask.waitTillComplete(); } diff --git a/test/src/com/aerospike/test/sync/TestSync.java b/test/src/com/aerospike/test/sync/TestSync.java index 97adac94d..1f6d1cf7c 100644 --- a/test/src/com/aerospike/test/sync/TestSync.java +++ b/test/src/com/aerospike/test/sync/TestSync.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -21,15 +21,15 @@ import org.junit.AfterClass; import org.junit.BeforeClass; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.Bin; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.test.SuiteSync; import com.aerospike.test.util.TestBase; public class TestSync extends TestBase { - protected static AerospikeClient client = SuiteSync.client; + protected static IAerospikeClient client = SuiteSync.client; private static boolean DestroyClient = false; @BeforeClass diff --git a/test/src/com/aerospike/test/sync/basic/TestAdd.java b/test/src/com/aerospike/test/sync/basic/TestAdd.java index 24210f095..774489a58 100644 --- a/test/src/com/aerospike/test/sync/basic/TestAdd.java +++ b/test/src/com/aerospike/test/sync/basic/TestAdd.java @@ -59,11 +59,13 @@ record = client.operate(null, key, Operation.add(bin), Operation.get(bin.name)); @Test public void addNullValue() { - Version version = Version.getServerVersion(client, null); + if (! args.useProxyClient) { + Version version = Version.getServerVersion(client, null); - // Do not run on servers < 3.6.1 - if (version.isLess(3, 6, 1)) { - return; + // Do not run on servers < 3.6.1 + if (version.isLess(3, 6, 1)) { + return; + } } Key key = new Key(args.namespace, args.set, "addkey"); diff --git a/test/src/com/aerospike/test/sync/basic/TestFilterExp.java b/test/src/com/aerospike/test/sync/basic/TestFilterExp.java index 27422b3e7..fdcadfa12 100644 --- a/test/src/com/aerospike/test/sync/basic/TestFilterExp.java +++ b/test/src/com/aerospike/test/sync/basic/TestFilterExp.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -63,6 +63,10 @@ public class TestFilterExp extends TestSync { @BeforeClass public static void register() { + if (args.useProxyClient) { + System.out.println("Skip TestFilterExp.register"); + return; + } RegisterTask task = client.register(null, TestUDF.class.getClassLoader(), "udf/record_example.lua", "record_example.lua", Language.LUA); @@ -309,6 +313,10 @@ public void run() { @Test public void udf() { + if (args.useProxyClient) { + System.out.println("Skip TestFilterExp.udf"); + return; + } WritePolicy policy = new WritePolicy(); policy.filterExp = Exp.build(Exp.eq(Exp.intBin(binA), Exp.val(1))); @@ -329,6 +337,10 @@ public void udf() { @Test public void udfExcept() { + if (args.useProxyClient) { + System.out.println("Skip TestFilterExp.udfExcept"); + return; + } WritePolicy policy = new WritePolicy(); policy.filterExp = Exp.build(Exp.eq(Exp.intBin(binA), Exp.val(1))); policy.failOnFilteredOut = true; diff --git a/test/src/com/aerospike/test/sync/basic/TestOperateBit.java b/test/src/com/aerospike/test/sync/basic/TestOperateBit.java index df291106b..3ffee323a 100644 --- a/test/src/com/aerospike/test/sync/basic/TestOperateBit.java +++ b/test/src/com/aerospike/test/sync/basic/TestOperateBit.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -710,11 +710,16 @@ public void assertBitModifyInsert(int bin_sz, int offset, int set_sz, @Test public void operateBitSetEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitSetEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; - for (int set_sz = 1; set_sz <= 80; set_sz++) { + for (int set_sz = 1; set_sz <= 10; set_sz++) { byte[] set_data = new byte[(set_sz + 7) / 8]; for (int offset = 0; offset <= (bin_bit_sz - set_sz); offset++) { @@ -727,6 +732,11 @@ public void operateBitSetEx() { @Test public void operateBitLShiftEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitLShiftEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; @@ -758,6 +768,11 @@ public void operateBitLShiftEx() { @Test public void operateBitRShiftEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitRShiftEx"); + return; + } + BitPolicy policy = new BitPolicy(); BitPolicy partial_policy = new BitPolicy(BitWriteFlags.PARTIAL); int bin_sz = 15; @@ -806,11 +821,16 @@ public void operateBitRShiftEx() { @Test public void operateBitAndEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitAndEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; - for (int set_sz = 1; set_sz <= 80; set_sz++) { + for (int set_sz = 1; set_sz <= 10; set_sz++) { byte[] set_data = new byte[(set_sz + 7) / 8]; for (int offset = 0; offset <= (bin_bit_sz - set_sz); offset++) { @@ -823,11 +843,16 @@ public void operateBitAndEx() { @Test public void operateBitNotEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitNotEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; - for (int set_sz = 1; set_sz <= 80; set_sz++) { + for (int set_sz = 1; set_sz <= 10; set_sz++) { byte[] set_data = new byte[(set_sz + 7) / 8]; for (int offset = 0; offset <= (bin_bit_sz - set_sz); offset++) { @@ -854,11 +879,16 @@ public void operateBitInsertEx() { @Test public void operateBitAddEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitAddEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; - for (int set_sz = 1; set_sz <= 64; set_sz++) { + for (int set_sz = 1; set_sz <= 10; set_sz++) { byte[] set_data = new byte[(set_sz + 7) / 8]; for (int offset = 0; offset <= (bin_bit_sz - set_sz); offset++) { @@ -871,11 +901,16 @@ public void operateBitAddEx() { @Test public void operateBitSubEx() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateBit.operateBitSubEx"); + return; + } + BitPolicy policy = new BitPolicy(); int bin_sz = 15; int bin_bit_sz = bin_sz * 8; - for (int set_sz = 1; set_sz <= 64; set_sz++) { + for (int set_sz = 1; set_sz <= 10; set_sz++) { byte[] expected = new byte[(set_sz + 7) / 8]; long value = 0xFFFFffffFFFFffffl >> (64 - set_sz); diff --git a/test/src/com/aerospike/test/sync/basic/TestOperateHll.java b/test/src/com/aerospike/test/sync/basic/TestOperateHll.java index 4ed8139f0..bd35a0179 100644 --- a/test/src/com/aerospike/test/sync/basic/TestOperateHll.java +++ b/test/src/com/aerospike/test/sync/basic/TestOperateHll.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -217,6 +217,11 @@ public void assertInit(int nIndexBits, int nMinhashBits, boolean shouldPass) { @Test public void operateHLLInit() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateHll.operateHLLInit"); + return; + } + client.delete(null, key); for (ArrayList desc : legalDescriptions) { @@ -330,6 +335,11 @@ public void assertAddInit(int nIndexBits, int nMinhashBits) { @Test public void operateHLLAddInit() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateHll.operateHLLAddInit"); + return; + } + for (ArrayList desc : legalDescriptions) { assertAddInit(desc.get(0), desc.get(1)); } @@ -727,6 +737,11 @@ record = assertSuccess("", key, @Test public void getPut() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateHll.getPut"); + return; + } + for (ArrayList desc : legalDescriptions) { int nIndexBits = desc.get(0); int nMinhashBits = desc.get(1); @@ -826,6 +841,11 @@ record = assertSuccess("similarity and intersectCount", key, @Test public void operateSimilarity() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateHll.operateSimilarity"); + return; + } + double[] overlaps = new double[] {0.0001, 0.001, 0.01, 0.1, 0.5}; for (double overlap : overlaps) { @@ -864,6 +884,11 @@ public void operateSimilarity() { @Test public void operateEmptySimilarity() { + if (args.useProxyClient) { + System.out.println("Skip TestOperateHll.operateEmptySimilarity"); + return; + } + for (ArrayList desc : legalDescriptions) { int nIndexBits = desc.get(0); int nMinhashBits = desc.get(1); diff --git a/test/src/com/aerospike/test/sync/basic/TestScan.java b/test/src/com/aerospike/test/sync/basic/TestScan.java index e8652e206..d0b3e1762 100644 --- a/test/src/com/aerospike/test/sync/basic/TestScan.java +++ b/test/src/com/aerospike/test/sync/basic/TestScan.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 Aerospike, Inc. + * Copyright 2012-2023 Aerospike, Inc. * * Portions may be licensed to Aerospike, Inc. under one or more contributor * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0. @@ -39,6 +39,10 @@ public void scanParallel() { @Test public void scanSeries() { + if (args.useProxyClient) { + System.out.println("Skip TestScan.scanSeries"); + return; + } ScanPolicy policy = new ScanPolicy(); List nodeList = client.getNodeNames(); diff --git a/test/src/com/aerospike/test/sync/basic/TestServerInfo.java b/test/src/com/aerospike/test/sync/basic/TestServerInfo.java index 07228e442..107972ecb 100644 --- a/test/src/com/aerospike/test/sync/basic/TestServerInfo.java +++ b/test/src/com/aerospike/test/sync/basic/TestServerInfo.java @@ -29,6 +29,10 @@ public class TestServerInfo extends TestSync { @Test public void serverInfo() { + if (args.useProxyClient) { + System.out.println("Skip TestServerInfo.serverInfo"); + return; + } Node node = client.getNodes()[0]; GetServerConfig(node); GetNamespaceConfig(node); diff --git a/test/src/com/aerospike/test/sync/basic/TestUDF.java b/test/src/com/aerospike/test/sync/basic/TestUDF.java index 036a94881..9e4459fce 100644 --- a/test/src/com/aerospike/test/sync/basic/TestUDF.java +++ b/test/src/com/aerospike/test/sync/basic/TestUDF.java @@ -48,6 +48,10 @@ public class TestUDF extends TestSync { @BeforeClass public static void register() { + if (args.useProxyClient) { + System.out.println("Skip TestUDF.register"); + return; + } RegisterTask task = client.register(null, TestUDF.class.getClassLoader(), "udf/record_example.lua", "record_example.lua", Language.LUA); task.waitTillComplete(); } diff --git a/test/src/com/aerospike/test/util/Args.java b/test/src/com/aerospike/test/util/Args.java index 1e24430e4..10c6d934e 100644 --- a/test/src/com/aerospike/test/util/Args.java +++ b/test/src/com/aerospike/test/util/Args.java @@ -27,14 +27,15 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; -import com.aerospike.client.AerospikeClient; import com.aerospike.client.AerospikeException; +import com.aerospike.client.IAerospikeClient; import com.aerospike.client.Info; import com.aerospike.client.Log; import com.aerospike.client.Log.Level; import com.aerospike.client.async.EventLoopType; import com.aerospike.client.cluster.Node; import com.aerospike.client.policy.AuthMode; +import com.aerospike.client.policy.ClientPolicy; import com.aerospike.client.policy.TlsPolicy; import com.aerospike.client.util.Util; @@ -50,8 +51,11 @@ public class Args { public String set; public TlsPolicy tlsPolicy; public EventLoopType eventLoopType = EventLoopType.DIRECT_NIO; + public int socketTimeout = 30000; + public int totalTimeout = 1000; public boolean enterprise; public boolean hasTtl; + public boolean useProxyClient; public Args() { host = "127.0.0.1"; @@ -112,6 +116,16 @@ public Args() { "Value: DIRECT_NIO | NETTY_NIO | NETTY_EPOLL | NETTY_KQUEUE | NETTY_IOURING" ); + options.addOption("st", "socketTimeout", true, + "Set read and write socketTimeout in milliseconds\n" + + "for single record and batch commands." + ); + options.addOption("tt", "totalTimeout", true, + "Set read and write totalTimeout in milliseconds\n" + + "for single record and batch commands." + ); + + options.addOption("proxy", false, "Use proxy client."); options.addOption("d", "debug", false, "Run in debug mode."); options.addOption("u", "usage", false, "Print usage."); @@ -187,6 +201,25 @@ public Args() { } } + if (cl.hasOption("socketTimeout")) { + socketTimeout = Integer.parseInt(cl.getOptionValue("socketTimeout"));; + } + + if (cl.hasOption("totalTimeout")) { + totalTimeout = Integer.parseInt(cl.getOptionValue("totalTimeout"));; + } + + if (cl.hasOption("proxy")) { + useProxyClient = true; + } + + // If the Aerospike server's default port (3000) is used and the proxy client is used, + // Reset the port to the proxy server's default port (4000). + if (port == 3000 && useProxyClient) { + System.out.println("Change proxy server port to 4000"); + port = 4000; + } + if (cl.hasOption("d")) { Log.setLevel(Level.DEBUG); } @@ -196,6 +229,21 @@ public Args() { } } + public void setClientPolicy(ClientPolicy p) { + p.user = user; + p.password = password; + p.authMode = authMode; + p.tlsPolicy = tlsPolicy; + p.readPolicyDefault.socketTimeout = socketTimeout; + p.readPolicyDefault.totalTimeout = totalTimeout; + p.writePolicyDefault.socketTimeout = socketTimeout; + p.writePolicyDefault.totalTimeout = totalTimeout; + p.batchPolicyDefault.socketTimeout = socketTimeout; + p.batchPolicyDefault.totalTimeout = totalTimeout; + p.batchParentPolicyWriteDefault.socketTimeout = socketTimeout; + p.batchParentPolicyWriteDefault.totalTimeout = totalTimeout; + } + private static void logUsage(Options options) { HelpFormatter formatter = new HelpFormatter(); StringWriter sw = new StringWriter(); @@ -208,7 +256,12 @@ private static void logUsage(Options options) { /** * Some database calls need to know how the server is configured. */ - public void setServerSpecific(AerospikeClient client) { + public void setServerSpecific(IAerospikeClient client) { + if (useProxyClient) { + // Proxy client does not support querying nodes directly for their configuration. + return; + } + Node node = client.getNodes()[0]; String editionFilter = "edition"; String namespaceFilter = "namespace/" + namespace;