Skip to content

Commit 9ccfe50

Browse files
committed
feat: Added memcached support
1 parent c41d07b commit 9ccfe50

File tree

4 files changed

+83
-3
lines changed

4 files changed

+83
-3
lines changed

core/redis-enterprise-admin/redis-enterprise-admin.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ dependencies {
22
implementation 'com.fasterxml.jackson.core:jackson-databind'
33
implementation 'org.apache.httpcomponents.client5:httpclient5'
44
implementation 'org.awaitility:awaitility'
5+
implementation group: 'net.spy', name: 'spymemcached', version: spymemcachedVersion
6+
57
testImplementation 'org.slf4j:slf4j-simple'
68
testImplementation 'org.junit.jupiter:junit-jupiter-api'
79
testImplementation 'org.junit.jupiter:junit-jupiter-params'

core/redis-enterprise-admin/src/main/java/com/redis/enterprise/Admin.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package com.redis.enterprise;
22

33
import java.io.IOException;
4+
import java.net.InetSocketAddress;
5+
import java.net.SocketAddress;
46
import java.net.URI;
57
import java.net.URISyntaxException;
68
import java.security.GeneralSecurityException;
79
import java.security.KeyManagementException;
810
import java.security.KeyStoreException;
911
import java.security.NoSuchAlgorithmException;
1012
import java.time.Duration;
13+
import java.util.Arrays;
1114
import java.util.Collection;
1215
import java.util.List;
1316
import java.util.Optional;
17+
import java.util.concurrent.atomic.AtomicBoolean;
1418
import java.util.stream.Stream;
1519

1620
import javax.net.ssl.SSLContext;
@@ -39,16 +43,22 @@
3943
import org.apache.hc.core5.http.io.entity.StringEntity;
4044
import org.apache.hc.core5.ssl.SSLContexts;
4145
import org.awaitility.Awaitility;
46+
import org.awaitility.core.ConditionFactory;
4247

4348
import com.fasterxml.jackson.databind.DeserializationFeature;
4449
import com.fasterxml.jackson.databind.JavaType;
4550
import com.fasterxml.jackson.databind.ObjectMapper;
4651
import com.fasterxml.jackson.databind.type.SimpleType;
4752
import com.redis.enterprise.Database.ModuleConfig;
53+
import com.redis.enterprise.Database.Type;
4854
import com.redis.enterprise.rest.Bootstrap;
4955
import com.redis.enterprise.rest.CommandResponse;
5056
import com.redis.enterprise.rest.InstalledModule;
5157

58+
import net.spy.memcached.ConnectionObserver;
59+
import net.spy.memcached.DefaultConnectionFactory;
60+
import net.spy.memcached.MemcachedConnection;
61+
5262
public class Admin implements AutoCloseable {
5363

5464
public static final String DEFAULT_USER_NAME = "admin@redis.com";
@@ -57,13 +67,16 @@ public class Admin implements AutoCloseable {
5767
public static final String DEFAULT_HOST = "localhost";
5868
public static final int DEFAULT_PORT = 9443;
5969

70+
private static final Command PING = Command.name("PING").build();
6071
private static final String BOOTSTRAP = "bootstrap";
6172
private static final String MODULES = "modules";
6273
private static final String BDBS = "bdbs";
6374
private static final String COMMAND = "command";
6475
private static final String CONTENT_TYPE_JSON = "application/json";
6576
private static final String V1 = "/v1/";
6677
private static final CharSequence PATH_SEPARATOR = "/";
78+
private static final Duration DEFAULT_DATABASE_CREATION_TIMEOUT = Duration.ofSeconds(10);
79+
private static final Duration DEFAULT_DATABASE_CREATION_POLL_INTERVAL = Duration.ofSeconds(1);
6780

6881
private final ObjectMapper objectMapper = new ObjectMapper()
6982
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@@ -73,6 +86,8 @@ public class Admin implements AutoCloseable {
7386
private String protocol = DEFAULT_PROTOCOL;
7487
private String host = DEFAULT_HOST;
7588
private int port = DEFAULT_PORT;
89+
private Duration databaseCreationTimeout = DEFAULT_DATABASE_CREATION_TIMEOUT;
90+
private Duration databaseCreationPollInterval = DEFAULT_DATABASE_CREATION_POLL_INTERVAL;
7691

7792
public void close() throws IOException {
7893
if (client != null) {
@@ -111,6 +126,16 @@ public Admin withProtocol(String protocol) {
111126
return this;
112127
}
113128

129+
public Admin withDatabaseCreationPollInterval(Duration interval) {
130+
this.databaseCreationPollInterval = interval;
131+
return this;
132+
}
133+
134+
public Admin withDatabaseCreationTimeout(Duration timeout) {
135+
this.databaseCreationTimeout = timeout;
136+
return this;
137+
}
138+
114139
public Admin withPort(int port) {
115140
this.port = port;
116141
return this;
@@ -228,9 +253,33 @@ public Database createDatabase(Database database) throws IOException, GeneralSec
228253
}
229254
}
230255
Database response = post(v1(BDBS), database, Database.class);
231-
long uid = response.getUid();
232-
Awaitility.await().pollInterval(Duration.ofSeconds(1)).ignoreExceptions()
233-
.until(() -> executeCommand(uid, new Command("PING")).getResponse().asBoolean());
256+
ConditionFactory await = Awaitility.await().pollInterval(databaseCreationPollInterval)
257+
.timeout(databaseCreationTimeout).ignoreExceptions();
258+
if (response.getType() == Type.REDIS) {
259+
await.until(() -> executeCommand(response.getUid(), PING).getResponse().asBoolean());
260+
} else {
261+
DefaultConnectionFactory connectionFactory = new DefaultConnectionFactory();
262+
MemcachedConnection connection = connectionFactory
263+
.createConnection(Arrays.asList(new InetSocketAddress(host, response.getPort())));
264+
AtomicBoolean connectionEstablished = new AtomicBoolean();
265+
try {
266+
connection.addObserver(new ConnectionObserver() {
267+
268+
@Override
269+
public void connectionLost(SocketAddress sa) {
270+
// do nothing
271+
}
272+
273+
@Override
274+
public void connectionEstablished(SocketAddress sa, int reconnectCount) {
275+
connectionEstablished.set(true);
276+
}
277+
});
278+
await.until(connectionEstablished::get);
279+
} finally {
280+
connection.shutdown();
281+
}
282+
}
234283
return response;
235284
}
236285

core/redis-enterprise-admin/src/main/java/com/redis/enterprise/Command.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.redis.enterprise;
22

33
import java.util.ArrayList;
4+
import java.util.Arrays;
45
import java.util.List;
56

67
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -34,4 +35,31 @@ public void setArgs(List<String> args) {
3435
this.args = args;
3536
}
3637

38+
public static Builder name(String name) {
39+
return new Builder(name);
40+
}
41+
42+
public static class Builder {
43+
44+
private final String command;
45+
private List<String> args = new ArrayList<>();
46+
47+
public Builder(String name) {
48+
this.command = name;
49+
}
50+
51+
public Builder args(String... args) {
52+
this.args = new ArrayList<>(Arrays.asList(args));
53+
return this;
54+
}
55+
56+
public Command build() {
57+
Command cmd = new Command();
58+
cmd.setCommand(this.command);
59+
cmd.setArgs(args);
60+
return cmd;
61+
}
62+
63+
}
64+
3765
}

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ kordampBuildVersion = 3.4.0
1212
kordampPluginVersion = 0.54.0
1313

1414
lettucemodVersion = 4.2.0
15+
spymemcachedVersion = 2.12.3
1516
testcontainersRedisVersion = 2.2.2
1617

1718
org.gradle.daemon = false

0 commit comments

Comments
 (0)