Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 3c40d9e

Browse files
authored
Merge pull request #491 from marklogic/feature/545-verify-connections
DEVEXP-545 Can now test connections
2 parents 8f898d5 + 2ca3b0d commit 3c40d9e

File tree

7 files changed

+608
-5
lines changed

7 files changed

+608
-5
lines changed

src/main/java/com/marklogic/appdeployer/command/CommandContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public Map<String, Object> getContextMap() {
120120
return contextMap;
121121
}
122122

123+
/**
124+
* @param contextMap
125+
* @deprecated since 4.6.0, will be removed in 5.0.0; the contextMap is not intended to be replaced.
126+
*/
127+
@Deprecated
123128
public void setContextMap(Map<String, Object> contextMap) {
124129
this.contextMap = contextMap;
125130
}
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
package com.marklogic.appdeployer.command;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.ArrayNode;
5+
import com.marklogic.appdeployer.AppConfig;
6+
import com.marklogic.client.DatabaseClient;
7+
import com.marklogic.client.ext.SecurityContextType;
8+
import com.marklogic.mgmt.ManageClient;
9+
import com.marklogic.mgmt.admin.AdminManager;
10+
import com.marklogic.mgmt.cma.ConfigurationManager;
11+
import com.marklogic.mgmt.resource.clusters.ClusterManager;
12+
import com.marklogic.rest.util.RestConfig;
13+
import org.springframework.web.client.DefaultResponseErrorHandler;
14+
import org.springframework.web.client.HttpClientErrorException;
15+
import org.springframework.web.client.ResponseErrorHandler;
16+
17+
import javax.net.ssl.SSLContext;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.function.Supplier;
21+
import java.util.stream.Stream;
22+
23+
/**
24+
* Command for testing each of the connections that can be made to MarkLogic based on the configuration in a
25+
* {@code CommandContext}.
26+
*
27+
* @since 4.6.0
28+
*/
29+
public class TestConnectionsCommand extends AbstractCommand {
30+
31+
/**
32+
* If run in a deployment process, this should run immediately so as to fail fast.
33+
*/
34+
public TestConnectionsCommand() {
35+
setExecuteSortOrder(0);
36+
}
37+
38+
/**
39+
* Can be included in a deployment process so that the deployment fails if any of the connections fail.
40+
*
41+
* @param context
42+
*/
43+
@Override
44+
public void execute(CommandContext context) {
45+
TestResults results = testConnections(context);
46+
if (results.anyTestFailed()) {
47+
throw new RuntimeException(results.toString());
48+
}
49+
logger.info(results.toString());
50+
}
51+
52+
/**
53+
* Intended for execution outside a deployment process, where the client wants access to the test results and
54+
* will choose how to present those to a user.
55+
*
56+
* @param context
57+
* @return
58+
*/
59+
public TestResults testConnections(CommandContext context) {
60+
try {
61+
TestResult manageResult = testManageAppServer(context.getManageClient());
62+
TestResult adminResult = testAdminAppServer(context.getAdminManager());
63+
64+
TestResult appServicesResult = null;
65+
TestResult restResult = null;
66+
TestResult testRestResult = null;
67+
if (manageResult.isSucceeded()) {
68+
List<Integer> serverPorts = getAppServerPorts(context.getManageClient());
69+
appServicesResult = testAppServicesAppServer(context.getAppConfig(), serverPorts);
70+
restResult = testRestAppServer(context.getAppConfig(), serverPorts);
71+
testRestResult = testTestRestAppServer(context.getAppConfig(), serverPorts);
72+
}
73+
74+
return new TestResults(manageResult, adminResult, appServicesResult, restResult, testRestResult);
75+
} catch (Exception ex) {
76+
// We don't expect any exceptions above, as each connection test has its own try/catch block.
77+
// This is simply to pretty up the error a bit.
78+
throw new RuntimeException("Unable to test connections; cause: " + ex.getMessage(), ex);
79+
}
80+
}
81+
82+
private List<Integer> getAppServerPorts(ManageClient manageClient) {
83+
JsonNode json = new ConfigurationManager(manageClient).getResourcesAsJson("server").getBody();
84+
ArrayNode servers = (ArrayNode) json.get("config").get(0).get("server");
85+
List<Integer> ports = new ArrayList<>();
86+
servers.forEach(server -> {
87+
if (server.has("port")) {
88+
ports.add(server.get("port").asInt());
89+
}
90+
});
91+
return ports;
92+
}
93+
94+
private TestResult testAppServicesAppServer(AppConfig appConfig, List<Integer> serverPorts) {
95+
if (appConfig.getAppServicesPort() != null && serverPorts.contains(appConfig.getAppServicesPort())) {
96+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getAppServicesPort(),
97+
appConfig.getAppServicesSslContext(), appConfig.getAppServicesSecurityContextType(),
98+
appConfig.getAppServicesUsername(), () -> appConfig.newAppServicesDatabaseClient(null));
99+
}
100+
return null;
101+
}
102+
103+
private TestResult testRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
104+
if (appConfig.getRestPort() != null && serverPorts.contains(appConfig.getRestPort())) {
105+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getRestPort(),
106+
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
107+
appConfig.getRestAdminUsername(), appConfig::newDatabaseClient);
108+
}
109+
return null;
110+
}
111+
112+
private TestResult testTestRestAppServer(AppConfig appConfig, List<Integer> serverPorts) {
113+
if (appConfig.getTestRestPort() != null && serverPorts.contains(appConfig.getTestRestPort())) {
114+
return testWithDatabaseClient(appConfig.getHost(), appConfig.getTestRestPort(),
115+
appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(),
116+
appConfig.getRestAdminUsername(), appConfig::newTestDatabaseClient);
117+
}
118+
return null;
119+
}
120+
121+
public static class TestResults {
122+
private TestResult manageTestResult;
123+
private TestResult adminTestResult;
124+
private TestResult appServicesTestResult;
125+
private TestResult restServerTestResult;
126+
private TestResult testRestServerTestResult;
127+
128+
public TestResults(TestResult manageTestResult, TestResult adminTestResult,
129+
TestResult appServicesTestResult, TestResult restServerTestResult, TestResult testRestServerTestResult) {
130+
this.manageTestResult = manageTestResult;
131+
this.adminTestResult = adminTestResult;
132+
this.appServicesTestResult = appServicesTestResult;
133+
this.restServerTestResult = restServerTestResult;
134+
this.testRestServerTestResult = testRestServerTestResult;
135+
}
136+
137+
public boolean anyTestFailed() {
138+
return Stream.of(manageTestResult, adminTestResult, appServicesTestResult, restServerTestResult, testRestServerTestResult)
139+
.anyMatch(test -> test != null && !test.isSucceeded());
140+
}
141+
142+
public TestResult getManageTestResult() {
143+
return manageTestResult;
144+
}
145+
146+
public TestResult getAdminTestResult() {
147+
return adminTestResult;
148+
}
149+
150+
public TestResult getAppServicesTestResult() {
151+
return appServicesTestResult;
152+
}
153+
154+
public TestResult getRestServerTestResult() {
155+
return restServerTestResult;
156+
}
157+
158+
public TestResult getTestRestServerTestResult() {
159+
return testRestServerTestResult;
160+
}
161+
162+
/**
163+
* @return a multi-line summary of all the non-null test results. This is intended to provide a simple
164+
* rendering of the test result data, suitable for use in ml-gradle.
165+
*/
166+
@Override
167+
public String toString() {
168+
StringBuilder sb = new StringBuilder();
169+
sb.append("Manage App Server\n").append(getManageTestResult())
170+
.append("\n\nAdmin App Server\n").append(getAdminTestResult());
171+
if (getManageTestResult().isSucceeded()) {
172+
if (getAppServicesTestResult() != null) {
173+
sb.append("\n\nApp-Services App Server\n").append(getAppServicesTestResult());
174+
} else {
175+
sb.append("\n\nNo test run for the App-Services App Server as either a port is not configured for it or it has not been deployed yet");
176+
}
177+
if (getRestServerTestResult() != null) {
178+
sb.append("\n\nREST API App Server\n").append(getRestServerTestResult());
179+
} else {
180+
sb.append("\n\nNo test run for a REST API App Server as either a port is not configured for it or it has not been deployed yet.");
181+
}
182+
if (getTestRestServerTestResult() != null) {
183+
sb.append("\n\nTest REST API App Server\n").append(getTestRestServerTestResult());
184+
} else {
185+
sb.append("\n\nNo test run for a Test REST API App Server as either a port is not configured for it or it has not been deployed yet.");
186+
}
187+
} else {
188+
sb.append("\n\nCould not test connections against the App-Services or REST API App Servers " +
189+
"due to the Manage App Server connection failing.");
190+
}
191+
return sb.toString();
192+
}
193+
}
194+
195+
public static class TestResult {
196+
private String host;
197+
private int port;
198+
private String scheme;
199+
private String authType;
200+
private String username;
201+
private boolean succeeded;
202+
private String message;
203+
204+
public TestResult(RestConfig restConfig, boolean succeeded, String message) {
205+
this(restConfig.getHost(), restConfig.getPort(), restConfig.getScheme(), restConfig.getAuthType(),
206+
restConfig.getUsername(), succeeded, message);
207+
}
208+
209+
public TestResult(RestConfig restConfig, Exception ex) {
210+
this(restConfig, false, ex.getMessage());
211+
}
212+
213+
public TestResult(String host, int port, String scheme, String authType, String username, DatabaseClient.ConnectionResult result) {
214+
this.host = host;
215+
this.port = port;
216+
this.scheme = scheme;
217+
this.authType = authType;
218+
this.username = username;
219+
this.succeeded = result.isConnected();
220+
if (!result.isConnected()) {
221+
this.message = String.format("Received %d: %s", result.getStatusCode(), result.getErrorMessage());
222+
}
223+
}
224+
225+
public TestResult(String host, int port, String scheme, String authType, String username, boolean succeeded, String message) {
226+
this.host = host;
227+
this.port = port;
228+
this.scheme = scheme;
229+
this.authType = authType;
230+
this.username = username;
231+
this.succeeded = succeeded;
232+
this.message = message;
233+
}
234+
235+
public String getHost() {
236+
return host;
237+
}
238+
239+
public int getPort() {
240+
return port;
241+
}
242+
243+
public String getScheme() {
244+
return scheme;
245+
}
246+
247+
public boolean isSucceeded() {
248+
return succeeded;
249+
}
250+
251+
public String getMessage() {
252+
return message;
253+
}
254+
255+
public String getAuthType() {
256+
return authType;
257+
}
258+
259+
public String getUsername() {
260+
return username;
261+
}
262+
263+
/**
264+
* @return a multi-line representation of the test result. This is intended to provide a simple
265+
* rendering of the test result data, suitable for use in ml-gradle.
266+
*/
267+
@Override
268+
public String toString() {
269+
String result = String.format("Configured to connect to %s://%s:%d using '%s' authentication",
270+
getScheme(), getHost(), getPort(), getAuthType());
271+
if (getUsername() != null) {
272+
result += String.format(" and username of '%s'", getUsername());
273+
}
274+
if (isSucceeded()) {
275+
result += "\nConnected successfully";
276+
return getMessage() != null ? result + "; " + getMessage() : result;
277+
}
278+
return result + "\nFAILED TO CONNECT; cause: " + message;
279+
}
280+
}
281+
282+
private TestResult testManageAppServer(ManageClient client) {
283+
ResponseErrorHandler originalErrorHandler = client.getRestTemplate().getErrorHandler();
284+
client.getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler());
285+
try {
286+
String version = new ClusterManager(client).getVersion();
287+
return new TestResult(client.getManageConfig(), true, "MarkLogic version: " + version);
288+
} catch (Exception ex) {
289+
if (ex instanceof HttpClientErrorException && ((HttpClientErrorException) ex).getRawStatusCode() == 404) {
290+
return new TestResult(client.getManageConfig(), false,
291+
"Unable to access /manage/v2; received 404; unexpected response: " + ex.getMessage());
292+
} else {
293+
return new TestResult(client.getManageConfig(), ex);
294+
}
295+
} finally {
296+
client.getRestTemplate().setErrorHandler(originalErrorHandler);
297+
}
298+
}
299+
300+
private TestResult testAdminAppServer(AdminManager adminManager) {
301+
try {
302+
String timestamp = adminManager.getServerTimestamp();
303+
return new TestResult(adminManager.getAdminConfig(), true, "MarkLogic server timestamp: " + timestamp);
304+
} catch (Exception ex) {
305+
return new TestResult(adminManager.getAdminConfig(), ex);
306+
}
307+
}
308+
309+
private TestResult testWithDatabaseClient(String host, Integer port, SSLContext sslContext,
310+
SecurityContextType securityContextType, String username, Supplier<DatabaseClient> supplier) {
311+
if (port == null) {
312+
return null;
313+
}
314+
final String scheme = sslContext != null ? "https" : "http";
315+
final String authType = securityContextType != null ? securityContextType.name().toLowerCase() : "unknown";
316+
DatabaseClient client = null;
317+
try {
318+
client = supplier.get();
319+
return new TestResult(host, port, scheme, authType, username, client.checkConnection());
320+
} catch (Exception ex) {
321+
return new TestResult(host, port, scheme, authType, username, false, ex.getMessage());
322+
} finally {
323+
if (client != null) {
324+
client.release();
325+
}
326+
}
327+
}
328+
}

src/main/java/com/marklogic/mgmt/admin/AdminManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ public String getServerVersion() {
236236
return getServerConfig().getElementValue("/m:host/m:version");
237237
}
238238

239+
/**
240+
*
241+
* @return
242+
* @since 4.6.0
243+
*/
244+
public String getServerTimestamp() {
245+
return getServerConfig().getElementValue("/m:host/m:timestamp");
246+
}
247+
239248
public void setWaitForRestartCheckInterval(int waitForRestartCheckInterval) {
240249
this.waitForRestartCheckInterval = waitForRestartCheckInterval;
241250
}

0 commit comments

Comments
 (0)