Skip to content

Commit 9543579

Browse files
committed
Added Admin::deleteDatabase method
1 parent 648cc93 commit 9543579

File tree

8 files changed

+167
-124
lines changed

8 files changed

+167
-124
lines changed

README.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
:project-name: redis-enterprise-admin
55
:project-group: com.redis.enterprise
66
:project-version: 0.1.0
7+
:tests-path: redis-field-engineering/redis-enterprise-admin/blob/master/subprojects/redis-enterprise-admin/src/test/java/com/redis/enterprise/
78

89
image:https://github.com/{project-owner}/{project-name}/actions/workflows/early-access.yml/badge.svg["Build Status", link="https://github.com/{project-owner}/{project-name}/actions"]
910
image:https://img.shields.io/maven-central/v/{project-group}/{project-name}.svg[Download, link="https://search.maven.org/#search|ga|1|{project-name}"]
@@ -31,3 +32,8 @@ dependencies {
3132
<scope>test</scope>
3233
</dependency>
3334
----
35+
36+
== Examples
37+
38+
Redis Enterprise Admin::
39+
https://github.com/{tests-path}/AdminTests.java[AdminTests.java]

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ dependencyPluginVersion=1.0.11.RELEASE
55
kordampBuildVersion=2.6.0
66
kordampPluginVersion=0.47.0
77

8+
awaitilityVersion=4.1.1
89
testcontainersRedisVersion=1.4.5

subprojects/redis-enterprise-admin/redis-enterprise-admin.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ dependencies {
22
implementation 'org.slf4j:slf4j-api'
33
implementation 'com.fasterxml.jackson.core:jackson-databind'
44
implementation 'org.apache.httpcomponents.client5:httpclient5'
5+
implementation group: 'org.awaitility', name: 'awaitility', version: awaitilityVersion
56
testImplementation 'org.slf4j:slf4j-simple'
67
testImplementation group: 'com.redis.testcontainers', name: 'testcontainers-redis-junit-jupiter', version: testcontainersRedisVersion
78
}

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

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.net.URI;
55
import java.net.URISyntaxException;
66
import java.security.GeneralSecurityException;
7+
import java.time.Duration;
78
import java.util.List;
89

910
import javax.net.ssl.SSLContext;
@@ -18,6 +19,7 @@
1819
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
1920
import org.apache.hc.client5.http.impl.auth.BasicScheme;
2021
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
22+
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
2123
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
2224
import org.apache.hc.client5.http.impl.classic.HttpClients;
2325
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
@@ -28,7 +30,6 @@
2830
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
2931
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
3032
import org.apache.hc.core5.http.ClassicHttpRequest;
31-
import org.apache.hc.core5.http.ClassicHttpResponse;
3233
import org.apache.hc.core5.http.ContentType;
3334
import org.apache.hc.core5.http.HttpHeaders;
3435
import org.apache.hc.core5.http.HttpHost;
@@ -37,6 +38,7 @@
3738
import org.apache.hc.core5.http.io.entity.EntityUtils;
3839
import org.apache.hc.core5.http.io.entity.StringEntity;
3940
import org.apache.hc.core5.ssl.SSLContexts;
41+
import org.awaitility.Awaitility;
4042
import org.slf4j.Logger;
4143
import org.slf4j.LoggerFactory;
4244

@@ -53,11 +55,11 @@
5355
import com.redis.enterprise.rest.Module;
5456
import com.redis.enterprise.rest.ModuleInstallResponse;
5557

56-
public class Admin {
58+
public class Admin implements AutoCloseable {
5759

5860
private static final Logger log = LoggerFactory.getLogger(Admin.class);
5961

60-
public static final Object CONTENT_TYPE_JSON = "application/json";
62+
public static final String CONTENT_TYPE_JSON = "application/json";
6163
public static final String V1 = "/v1/";
6264
public static final String V2 = "/v2/";
6365
public static final String DEFAULT_PROTOCOL = "https";
@@ -73,12 +75,26 @@ public class Admin {
7375
private final ObjectMapper objectMapper = new ObjectMapper()
7476
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
7577
private final UsernamePasswordCredentials credentials;
78+
private final CloseableHttpClient client;
7679
private String protocol = DEFAULT_PROTOCOL;
7780
private String host = DEFAULT_HOST;
7881
private int port = DEFAULT_PORT;
7982

80-
public Admin(String userName, final char[] password) {
83+
public Admin(String userName, final char[] password) throws GeneralSecurityException {
8184
this.credentials = new UsernamePasswordCredentials(userName, password);
85+
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
86+
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
87+
.setSslContext(sslcontext).setHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();
88+
HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
89+
.setSSLSocketFactory(sslSocketFactory).build();
90+
HttpClientBuilder clientBuilder = HttpClients.custom();
91+
clientBuilder.setConnectionManager(cm);
92+
this.client = clientBuilder.build();
93+
}
94+
95+
@Override
96+
public void close() throws Exception {
97+
client.close();
8298
}
8399

84100
public void setHost(String host) {
@@ -113,97 +129,95 @@ private URI uri(String path) {
113129
}
114130
}
115131

116-
private <T> T get(String path, Class<T> type) throws ParseException, GeneralSecurityException, IOException {
132+
private <T> T get(String path, Class<T> type) throws ParseException, IOException {
117133
return get(path, SimpleType.constructUnsafe(type));
118134
}
119135

120-
private <T> T get(String path, JavaType type) throws ParseException, GeneralSecurityException, IOException {
121-
return read(new HttpGet(uri(path)), type);
136+
private <T> T get(String path, JavaType type) throws ParseException, IOException {
137+
return read(new HttpGet(uri(path)), type, HttpStatus.SC_OK);
122138
}
123139

124-
private <T> T post(String path, Object request, Class<T> responseType)
125-
throws ParseException, GeneralSecurityException, IOException {
126-
return post(path, request, SimpleType.constructUnsafe(responseType));
140+
private <T> T delete(String path, Class<T> type) throws ParseException, IOException {
141+
return delete(path, SimpleType.constructUnsafe(type));
142+
}
143+
144+
private <T> T delete(String path, JavaType type) throws ParseException, IOException {
145+
return read(new HttpDelete(uri(path)), type, HttpStatus.SC_OK);
127146
}
128147

129-
private ClassicHttpResponse delete(String path) throws GeneralSecurityException, IOException {
130-
return execute(new HttpDelete(uri(path)));
148+
private <T> T post(String path, Object request, Class<T> responseType) throws ParseException, IOException {
149+
return post(path, request, SimpleType.constructUnsafe(responseType));
131150
}
132151

133-
private <T> T post(String path, Object request, JavaType responseType)
134-
throws ParseException, GeneralSecurityException, IOException {
152+
private <T> T post(String path, Object request, JavaType responseType) throws ParseException, IOException {
135153
HttpPost post = new HttpPost(uri(path));
136154
String json = objectMapper.writeValueAsString(request);
137155
post.setEntity(new StringEntity(json));
138-
log.info("POST {}", json);
139-
return read(post, responseType);
140-
}
141-
142-
private <T> T read(ClassicHttpRequest request, JavaType type)
143-
throws ParseException, GeneralSecurityException, IOException {
144-
request.setHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON);
145-
return read(request, type, HttpStatus.SC_OK);
156+
return read(post, responseType, HttpStatus.SC_OK);
146157
}
147158

148-
private <T> T read(ClassicHttpRequest request, Class<T> type, int successCode)
149-
throws ParseException, GeneralSecurityException, IOException {
159+
private <T> T read(ClassicHttpRequest request, Class<T> type, int successCode) throws ParseException, IOException {
150160
return read(request, SimpleType.constructUnsafe(type), successCode);
151161
}
152162

153-
private <T> T read(ClassicHttpRequest request, JavaType type, int successCode)
154-
throws GeneralSecurityException, IOException, ParseException {
155-
ClassicHttpResponse response = execute(request);
163+
private <T> T read(ClassicHttpRequest request, JavaType type, int successCode) throws IOException, ParseException {
164+
request.setHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON);
165+
HttpHost target = new HttpHost(protocol, host, port);
166+
HttpClientContext localContext = HttpClientContext.create();
167+
if (credentials != null) {
168+
BasicScheme basicAuth = new BasicScheme();
169+
basicAuth.initPreemptive(credentials);
170+
localContext.resetAuthExchange(target, basicAuth);
171+
}
172+
CloseableHttpResponse response = client.execute(request, localContext);
156173
if (response.getCode() == successCode) {
157174
return objectMapper.readValue(EntityUtils.toString(response.getEntity()), type);
158175
}
159176
throw new HttpResponseException(response.getCode(), response.getReasonPhrase());
160177
}
161178

162-
private ClassicHttpResponse execute(ClassicHttpRequest request) throws GeneralSecurityException, IOException {
163-
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
164-
SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
165-
.setSslContext(sslcontext).setHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();
166-
HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
167-
.setSSLSocketFactory(sslSocketFactory).build();
168-
HttpClientBuilder clientBuilder = HttpClients.custom();
169-
clientBuilder.setConnectionManager(cm);
170-
try (CloseableHttpClient client = clientBuilder.build()) {
171-
HttpHost target = new HttpHost(protocol, host, port);
172-
HttpClientContext localContext = HttpClientContext.create();
173-
if (credentials != null) {
174-
BasicScheme basicAuth = new BasicScheme();
175-
basicAuth.initPreemptive(credentials);
176-
localContext.resetAuthExchange(target, basicAuth);
177-
}
178-
return client.execute(request, localContext);
179-
}
180-
}
181-
182-
public List<Module> getModules() throws ParseException, GeneralSecurityException, IOException {
179+
public List<Module> getModules() throws ParseException, IOException {
183180
CollectionType type = objectMapper.getTypeFactory().constructCollectionType(List.class, Module.class);
184181
return get(v1(MODULES), type);
185182
}
186183

187-
public Database createDatabase(Database database)
188-
throws ParseException, GeneralSecurityException, IOException {
189-
return post(v1(BDBS), database, Database.class);
184+
public Database createDatabase(Database database) throws ParseException, IOException {
185+
Database response = post(v1(BDBS), database, Database.class);
186+
long uid = response.getUid();
187+
Awaitility.await().until(() -> {
188+
Command command = new Command();
189+
command.setCommand("PING");
190+
try {
191+
return executeCommand(uid, command).getResponse().asBoolean();
192+
} catch (HttpResponseException e) {
193+
log.info("PING unsuccessful, retrying...");
194+
return false;
195+
}
196+
});
197+
return response;
190198
}
191199

192-
public List<Database> getDatabases() throws ParseException, GeneralSecurityException, IOException {
200+
public List<Database> getDatabases() throws ParseException, IOException {
193201
CollectionType type = objectMapper.getTypeFactory().constructCollectionType(List.class, Database.class);
194202
return get(v1(BDBS), type);
195203
}
196204

197-
public void deleteDatabase(long uid) throws GeneralSecurityException, IOException {
198-
ClassicHttpResponse response = delete(v1(BDBS, String.valueOf(uid)));
199-
if (response.getCode() != HttpStatus.SC_OK) {
200-
throw new HttpResponseException(response.getCode(), response.getReasonPhrase());
201-
}
202-
log.info("Deleted database {}", uid);
205+
public void deleteDatabase(long uid) {
206+
Awaitility.await().timeout(Duration.ofSeconds(30)).pollInterval(Duration.ofSeconds(1)).until(() -> {
207+
try {
208+
delete(v1(BDBS, String.valueOf(uid)), Database.class);
209+
return true;
210+
} catch (HttpResponseException e) {
211+
if (e.getStatusCode() == HttpStatus.SC_CONFLICT) {
212+
log.info("Could not delete database {}, retrying...", uid);
213+
return false;
214+
}
215+
throw e;
216+
}
217+
});
203218
}
204219

205-
public ModuleInstallResponse installModule(String filename, byte[] bytes)
206-
throws ParseException, GeneralSecurityException, IOException {
220+
public ModuleInstallResponse installModule(String filename, byte[] bytes) throws ParseException, IOException {
207221
HttpPost post = new HttpPost(uri(v2(MODULES)));
208222
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
209223
builder.setMode(HttpMultipartMode.STRICT);
@@ -212,16 +226,15 @@ public ModuleInstallResponse installModule(String filename, byte[] bytes)
212226
return read(post, ModuleInstallResponse.class, HttpStatus.SC_ACCEPTED);
213227
}
214228

215-
public Bootstrap getBootstrap() throws ParseException, GeneralSecurityException, IOException {
229+
public Bootstrap getBootstrap() throws ParseException, IOException {
216230
return get(v1(BOOTSTRAP), Bootstrap.class);
217231
}
218232

219-
public Action getAction(String uid) throws ParseException, GeneralSecurityException, IOException {
233+
public Action getAction(String uid) throws ParseException, IOException {
220234
return get(v1(ACTIONS, uid), Action.class);
221235
}
222236

223-
public CommandResponse executeCommand(long bdb, Command command)
224-
throws ParseException, GeneralSecurityException, IOException {
237+
public CommandResponse executeCommand(long bdb, Command command) throws ParseException, IOException {
225238
return post(v1(BDBS, String.valueOf(bdb), COMMAND), command, CommandResponse.class);
226239
}
227240

subprojects/redis-enterprise-admin/src/main/java/com/redis/enterprise/rest/Command.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,4 @@ public void setArgs(List<String> args) {
2727
this.args = args;
2828
}
2929

30-
public static Command of(String commandString) {
31-
Command command = new Command();
32-
command.setCommand(commandString);
33-
return command;
34-
}
35-
3630
}

subprojects/redis-enterprise-admin/src/main/java/com/redis/enterprise/rest/Database.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.redis.enterprise.rest;
22

3-
import java.util.ArrayList;
43
import java.util.List;
54
import java.util.stream.Collectors;
65
import java.util.stream.Stream;
@@ -29,10 +28,10 @@ public class Database {
2928
private Boolean ossCluster;
3029
private ProxyPolicy proxyPolicy;
3130
private IPType ossClusterAPIPreferredIPType;
32-
private List<Regex> shardKeyRegex = new ArrayList<>();
31+
private List<Regex> shardKeyRegex;
3332
private Integer shardCount;
3433
private ShardPlacement shardPlacement;
35-
private List<ModuleConfig> moduleConfigs = new ArrayList<>();
34+
private List<ModuleConfig> moduleConfigs;
3635

3736
public Long getUid() {
3837
return uid;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.redis.enterprise;
2+
3+
import java.io.IOException;
4+
import java.security.GeneralSecurityException;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
7+
import java.util.stream.Stream;
8+
9+
import org.apache.hc.core5.http.ParseException;
10+
import org.awaitility.Awaitility;
11+
import org.junit.jupiter.api.AfterAll;
12+
import org.junit.jupiter.api.Assertions;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.TestInstance;
17+
import org.junit.jupiter.api.TestInstance.Lifecycle;
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import org.testcontainers.junit.jupiter.Container;
21+
import org.testcontainers.junit.jupiter.Testcontainers;
22+
23+
import com.redis.enterprise.rest.Database;
24+
import com.redis.testcontainers.RedisEnterpriseContainer;
25+
26+
@Testcontainers
27+
@TestInstance(Lifecycle.PER_CLASS)
28+
class AdminTests {
29+
30+
private static final Logger log = LoggerFactory.getLogger(AdminTests.class);
31+
32+
@Container
33+
private static RedisEnterpriseContainer server = new RedisEnterpriseContainer();
34+
35+
private static Admin admin;
36+
37+
@BeforeAll
38+
static void setupAdmin() throws ParseException, GeneralSecurityException, IOException {
39+
admin = new Admin(RedisEnterpriseContainer.ADMIN_USERNAME,
40+
RedisEnterpriseContainer.ADMIN_PASSWORD.toCharArray());
41+
admin.setHost(server.getHost());
42+
}
43+
44+
@AfterAll
45+
static void teardownAdmin() throws Exception {
46+
admin.close();
47+
}
48+
49+
@BeforeEach
50+
void deleteAllDatabases() throws GeneralSecurityException, IOException, ParseException {
51+
List<Database> databases = admin.getDatabases();
52+
log.info("Deleting databases {}", databases.stream().map(Database::getUid).collect(Collectors.toList()));
53+
for (Database database : databases) {
54+
admin.deleteDatabase(database.getUid());
55+
}
56+
Awaitility.await().until(() -> admin.getDatabases().isEmpty());
57+
}
58+
59+
@Test
60+
void createDatabase() throws ParseException, GeneralSecurityException, IOException {
61+
String databaseName = "CreateTestDB";
62+
Database request = new Database();
63+
request.setName(databaseName);
64+
request.setOssCluster(true);
65+
admin.createDatabase(request);
66+
Stream<Database> stream = admin.getDatabases().stream().filter(d -> d.getName().equals(databaseName));
67+
Assertions.assertEquals(1, stream.count());
68+
}
69+
70+
@Test
71+
void deleteDatabase() throws ParseException, GeneralSecurityException, IOException {
72+
String databaseName = "DeleteTestDB";
73+
Database request = new Database();
74+
request.setName(databaseName);
75+
Database database = admin.createDatabase(request);
76+
admin.deleteDatabase(database.getUid());
77+
Awaitility.await().until(() -> admin.getDatabases().stream().noneMatch(d -> d.getUid() == database.getUid()));
78+
}
79+
80+
}

0 commit comments

Comments
 (0)